Commit c71035c7f306244e716c2bc1132ca7174d212b25

Authored by m-holger
1 parent a70158a3

Refactor `QPDFWriter`: move methods `prepareFileForWrite`, `enqueueObject`, `enq…

…ueueObjectsStandard`, `enqueueObjectsPCLm`, `enqueuePart`, `assignCompressedObjectNumbers`, and `getTrimmedTrailer` to `QPDFWriter::Members`. Update related logic and remove obsolete test coverage entries.
include/qpdf/QPDFWriter.hh
@@ -471,8 +471,6 @@ class QPDFWriter @@ -471,8 +471,6 @@ class QPDFWriter
471 QPDFWriter& write_qdf(Args&&... args); 471 QPDFWriter& write_qdf(Args&&... args);
472 template <typename... Args> 472 template <typename... Args>
473 QPDFWriter& write_no_qdf(Args&&... args); 473 QPDFWriter& write_no_qdf(Args&&... args);
474 - void assignCompressedObjectNumbers(QPDFObjGen og);  
475 - void enqueueObject(QPDFObjectHandle object);  
476 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj); 474 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
477 void writeObjectStream(QPDFObjectHandle object); 475 void writeObjectStream(QPDFObjectHandle object);
478 void writeObject(QPDFObjectHandle object, int object_stream_index = -1); 476 void writeObject(QPDFObjectHandle object, int object_stream_index = -1);
@@ -505,14 +503,9 @@ class QPDFWriter @@ -505,14 +503,9 @@ class QPDFWriter
505 void setDataKey(int objid); 503 void setDataKey(int objid);
506 int openObject(int objid = 0); 504 int openObject(int objid = 0);
507 void closeObject(int objid); 505 void closeObject(int objid);
508 - QPDFObjectHandle getTrimmedTrailer();  
509 - void prepareFileForWrite();  
510 - void enqueueObjectsStandard();  
511 - void enqueueObjectsPCLm();  
512 void indicateProgress(bool decrement, bool finished); 506 void indicateProgress(bool decrement, bool finished);
513 void writeStandard(); 507 void writeStandard();
514 void writeLinearized(); 508 void writeLinearized();
515 - void enqueuePart(std::vector<QPDFObjectHandle>& part);  
516 void writeEncryptionDictionary(); 509 void writeEncryptionDictionary();
517 void writeHeader(); 510 void writeHeader();
518 void writeHintStream(int hint_id); 511 void writeHintStream(int hint_id);
libqpdf/QPDFWriter.cc
@@ -287,6 +287,7 @@ class QPDFWriter::Members @@ -287,6 +287,7 @@ class QPDFWriter::Members
287 void setMinimumPDFVersion(std::string const& version, int extension_level); 287 void setMinimumPDFVersion(std::string const& version, int extension_level);
288 void copyEncryptionParameters(QPDF&); 288 void copyEncryptionParameters(QPDF&);
289 void doWriteSetup(); 289 void doWriteSetup();
  290 + void prepareFileForWrite();
290 291
291 void disableIncompatibleEncryption(int major, int minor, int extension_level); 292 void disableIncompatibleEncryption(int major, int minor, int extension_level);
292 void parseVersion(std::string const& version, int& major, int& minor) const; 293 void parseVersion(std::string const& version, int& major, int& minor) const;
@@ -297,6 +298,12 @@ class QPDFWriter::Members @@ -297,6 +298,12 @@ class QPDFWriter::Members
297 void preserveObjectStreams(); 298 void preserveObjectStreams();
298 void generateObjectStreams(); 299 void generateObjectStreams();
299 void initializeSpecialStreams(); 300 void initializeSpecialStreams();
  301 + void enqueueObject(QPDFObjectHandle object);
  302 + void enqueueObjectsStandard();
  303 + void enqueueObjectsPCLm();
  304 + void enqueuePart(std::vector<QPDFObjectHandle>& part);
  305 + void assignCompressedObjectNumbers(QPDFObjGen og);
  306 + QPDFObjectHandle getTrimmedTrailer();
300 307
301 private: 308 private:
302 QPDFWriter& w; 309 QPDFWriter& w;
@@ -1170,76 +1177,74 @@ QPDFWriter::closeObject(int objid) @@ -1170,76 +1177,74 @@ QPDFWriter::closeObject(int objid)
1170 } 1177 }
1171 1178
1172 void 1179 void
1173 -QPDFWriter::assignCompressedObjectNumbers(QPDFObjGen og) 1180 +QPDFWriter::Members::assignCompressedObjectNumbers(QPDFObjGen og)
1174 { 1181 {
1175 int objid = og.getObj(); 1182 int objid = og.getObj();
1176 - if ((og.getGen() != 0) || (!m->object_stream_to_objects.contains(objid))) { 1183 + if (og.getGen() != 0 || !object_stream_to_objects.contains(objid)) {
1177 // This is not an object stream. 1184 // This is not an object stream.
1178 return; 1185 return;
1179 } 1186 }
1180 1187
1181 // Reserve numbers for the objects that belong to this object stream. 1188 // Reserve numbers for the objects that belong to this object stream.
1182 - for (auto const& iter: m->object_stream_to_objects[objid]) {  
1183 - m->obj[iter].renumber = m->next_objid++; 1189 + for (auto const& iter: object_stream_to_objects[objid]) {
  1190 + obj[iter].renumber = next_objid++;
1184 } 1191 }
1185 } 1192 }
1186 1193
1187 void 1194 void
1188 -QPDFWriter::enqueueObject(QPDFObjectHandle object) 1195 +QPDFWriter::Members::enqueueObject(QPDFObjectHandle object)
1189 { 1196 {
1190 if (object.isIndirect()) { 1197 if (object.isIndirect()) {
1191 // This owner check can only be done for indirect objects. It is possible for a direct 1198 // This owner check can only be done for indirect objects. It is possible for a direct
1192 // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle from 1199 // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle from
1193 // one file was insert into another file without copying. Doing that is safe even if the 1200 // one file was insert into another file without copying. Doing that is safe even if the
1194 // original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from its owner. 1201 // original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from its owner.
1195 - if (object.getOwningQPDF() != &(m->pdf)) {  
1196 - QTC::TC("qpdf", "QPDFWriter foreign object"); 1202 + if (object.getOwningQPDF() != &pdf) {
1197 throw std::logic_error( 1203 throw std::logic_error(
1198 "QPDFObjectHandle from different QPDF found while writing. Use " 1204 "QPDFObjectHandle from different QPDF found while writing. Use "
1199 "QPDF::copyForeignObject to add objects from another file."); 1205 "QPDF::copyForeignObject to add objects from another file.");
1200 } 1206 }
1201 1207
1202 - if (m->qdf_mode && object.isStreamOfType("/XRef")) { 1208 + if (qdf_mode && object.isStreamOfType("/XRef")) {
1203 // As a special case, do not output any extraneous XRef streams in QDF mode. Doing so 1209 // As a special case, do not output any extraneous XRef streams in QDF mode. Doing so
1204 // will confuse fix-qdf, which expects to see only one XRef stream at the end of the 1210 // will confuse fix-qdf, which expects to see only one XRef stream at the end of the
1205 // file. This case can occur when creating a QDF from a file with object streams when 1211 // file. This case can occur when creating a QDF from a file with object streams when
1206 // preserving unreferenced objects since the old cross reference streams are not 1212 // preserving unreferenced objects since the old cross reference streams are not
1207 // actually referenced by object number. 1213 // actually referenced by object number.
1208 - QTC::TC("qpdf", "QPDFWriter ignore XRef in qdf mode");  
1209 return; 1214 return;
1210 } 1215 }
1211 1216
1212 QPDFObjGen og = object.getObjGen(); 1217 QPDFObjGen og = object.getObjGen();
1213 - auto& obj = m->obj[og]; 1218 + auto& o = obj[og];
1214 1219
1215 - if (obj.renumber == 0) {  
1216 - if (obj.object_stream > 0) { 1220 + if (o.renumber == 0) {
  1221 + if (o.object_stream > 0) {
1217 // This is in an object stream. Don't process it here. Instead, enqueue the object 1222 // This is in an object stream. Don't process it here. Instead, enqueue the object
1218 // stream. Object streams always have generation 0. 1223 // stream. Object streams always have generation 0.
1219 // Detect loops by storing invalid object ID -1, which will get overwritten later. 1224 // Detect loops by storing invalid object ID -1, which will get overwritten later.
1220 - obj.renumber = -1;  
1221 - enqueueObject(m->pdf.getObject(obj.object_stream, 0)); 1225 + o.renumber = -1;
  1226 + enqueueObject(pdf.getObject(o.object_stream, 0));
1222 } else { 1227 } else {
1223 - m->object_queue.push_back(object);  
1224 - obj.renumber = m->next_objid++; 1228 + object_queue.emplace_back(object);
  1229 + o.renumber = next_objid++;
1225 1230
1226 - if ((og.getGen() == 0) && m->object_stream_to_objects.contains(og.getObj())) { 1231 + if (og.getGen() == 0 && object_stream_to_objects.contains(og.getObj())) {
1227 // For linearized files, uncompressed objects go at end, and we take care of 1232 // For linearized files, uncompressed objects go at end, and we take care of
1228 // assigning numbers to them elsewhere. 1233 // assigning numbers to them elsewhere.
1229 - if (!m->linearized) { 1234 + if (!linearized) {
1230 assignCompressedObjectNumbers(og); 1235 assignCompressedObjectNumbers(og);
1231 } 1236 }
1232 - } else if ((!m->direct_stream_lengths) && object.isStream()) { 1237 + } else if (!direct_stream_lengths && object.isStream()) {
1233 // reserve next object ID for length 1238 // reserve next object ID for length
1234 - ++m->next_objid; 1239 + ++next_objid;
1235 } 1240 }
1236 } 1241 }
1237 - } else if (obj.renumber == -1) { 1242 + } else if (o.renumber == -1) {
1238 // This can happen if a specially constructed file indicates that an object stream is 1243 // This can happen if a specially constructed file indicates that an object stream is
1239 // inside itself. 1244 // inside itself.
1240 } 1245 }
1241 return; 1246 return;
1242 - } else if (!m->linearized) { 1247 + } else if (!linearized) {
1243 if (object.isArray()) { 1248 if (object.isArray()) {
1244 for (auto& item: object.as_array()) { 1249 for (auto& item: object.as_array()) {
1245 enqueueObject(item); 1250 enqueueObject(item);
@@ -1260,7 +1265,7 @@ void @@ -1260,7 +1265,7 @@ void
1260 QPDFWriter::unparseChild(QPDFObjectHandle const& child, size_t level, int flags) 1265 QPDFWriter::unparseChild(QPDFObjectHandle const& child, size_t level, int flags)
1261 { 1266 {
1262 if (!m->linearized) { 1267 if (!m->linearized) {
1263 - enqueueObject(child); 1268 + m->enqueueObject(child);
1264 } 1269 }
1265 if (child.isIndirect()) { 1270 if (child.isIndirect()) {
1266 write(m->obj[child].renumber).write(" 0 R"); 1271 write(m->obj[child].renumber).write(" 0 R");
@@ -1273,7 +1278,7 @@ void @@ -1273,7 +1278,7 @@ void
1273 QPDFWriter::writeTrailer( 1278 QPDFWriter::writeTrailer(
1274 trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass) 1279 trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass)
1275 { 1280 {
1276 - QPDFObjectHandle trailer = getTrimmedTrailer(); 1281 + QPDFObjectHandle trailer = m->getTrimmedTrailer();
1277 if (xref_stream) { 1282 if (xref_stream) {
1278 m->cur_data_key.clear(); 1283 m->cur_data_key.clear();
1279 } else { 1284 } else {
@@ -2082,11 +2087,11 @@ QPDFWriter::Members::generateObjectStreams() @@ -2082,11 +2087,11 @@ QPDFWriter::Members::generateObjectStreams()
2082 } 2087 }
2083 2088
2084 QPDFObjectHandle 2089 QPDFObjectHandle
2085 -QPDFWriter::getTrimmedTrailer() 2090 +QPDFWriter::Members::getTrimmedTrailer()
2086 { 2091 {
2087 // Remove keys from the trailer that necessarily have to be replaced when writing the file. 2092 // Remove keys from the trailer that necessarily have to be replaced when writing the file.
2088 2093
2089 - QPDFObjectHandle trailer = m->pdf.getTrailer().unsafeShallowCopy(); 2094 + QPDFObjectHandle trailer = pdf.getTrailer().unsafeShallowCopy();
2090 2095
2091 // Remove encryption keys 2096 // Remove encryption keys
2092 trailer.removeKey("/ID"); 2097 trailer.removeKey("/ID");
@@ -2109,10 +2114,10 @@ QPDFWriter::getTrimmedTrailer() @@ -2109,10 +2114,10 @@ QPDFWriter::getTrimmedTrailer()
2109 2114
2110 // Make document extension level information direct as required by the spec. 2115 // Make document extension level information direct as required by the spec.
2111 void 2116 void
2112 -QPDFWriter::prepareFileForWrite() 2117 +QPDFWriter::Members::prepareFileForWrite()
2113 { 2118 {
2114 - m->pdf.fixDanglingReferences();  
2115 - auto root = m->pdf.getRoot(); 2119 + pdf.fixDanglingReferences();
  2120 + auto root = pdf.getRoot();
2116 auto oh = root.getKey("/Extensions"); 2121 auto oh = root.getKey("/Extensions");
2117 if (oh.isDictionary()) { 2122 if (oh.isDictionary()) {
2118 const bool extensions_indirect = oh.isIndirect(); 2123 const bool extensions_indirect = oh.isIndirect();
@@ -2280,7 +2285,7 @@ QPDFWriter::write() @@ -2280,7 +2285,7 @@ QPDFWriter::write()
2280 // approximation, but it's good enough for progress reporting, which is mostly a guess anyway. 2285 // approximation, but it's good enough for progress reporting, which is mostly a guess anyway.
2281 m->events_expected = QIntC::to_int(m->pdf.getObjectCount() * (m->linearized ? 2 : 1)); 2286 m->events_expected = QIntC::to_int(m->pdf.getObjectCount() * (m->linearized ? 2 : 1));
2282 2287
2283 - prepareFileForWrite(); 2288 + m->prepareFileForWrite();
2284 2289
2285 if (m->linearized) { 2290 if (m->linearized) {
2286 writeLinearized(); 2291 writeLinearized();
@@ -2321,7 +2326,7 @@ QPDFWriter::getWrittenXRefTable() @@ -2321,7 +2326,7 @@ QPDFWriter::getWrittenXRefTable()
2321 } 2326 }
2322 2327
2323 void 2328 void
2324 -QPDFWriter::enqueuePart(std::vector<QPDFObjectHandle>& part) 2329 +QPDFWriter::Members::enqueuePart(std::vector<QPDFObjectHandle>& part)
2325 { 2330 {
2326 for (auto const& oh: part) { 2331 for (auto const& oh: part) {
2327 enqueueObject(oh); 2332 enqueueObject(oh);
@@ -2650,7 +2655,7 @@ QPDFWriter::writeLinearized() @@ -2650,7 +2655,7 @@ QPDFWriter::writeLinearized()
2650 std::vector<QPDFObjectHandle>* vecs2[] = {&part7, &part8, &part9}; 2655 std::vector<QPDFObjectHandle>* vecs2[] = {&part7, &part8, &part9};
2651 for (int i = 0; i < 3; ++i) { 2656 for (int i = 0; i < 3; ++i) {
2652 for (auto const& oh: *vecs2[i]) { 2657 for (auto const& oh: *vecs2[i]) {
2653 - assignCompressedObjectNumbers(oh.getObjGen()); 2658 + m->assignCompressedObjectNumbers(oh.getObjGen());
2654 } 2659 }
2655 } 2660 }
2656 int second_half_end = m->next_objid - 1; 2661 int second_half_end = m->next_objid - 1;
@@ -2677,7 +2682,7 @@ QPDFWriter::writeLinearized() @@ -2677,7 +2682,7 @@ QPDFWriter::writeLinearized()
2677 std::vector<QPDFObjectHandle>* vecs1[] = {&part4, &part6}; 2682 std::vector<QPDFObjectHandle>* vecs1[] = {&part4, &part6};
2678 for (int i = 0; i < 2; ++i) { 2683 for (int i = 0; i < 2; ++i) {
2679 for (auto const& oh: *vecs1[i]) { 2684 for (auto const& oh: *vecs1[i]) {
2680 - assignCompressedObjectNumbers(oh.getObjGen()); 2685 + m->assignCompressedObjectNumbers(oh.getObjGen());
2681 } 2686 }
2682 } 2687 }
2683 int first_half_end = m->next_objid - 1; 2688 int first_half_end = m->next_objid - 1;
@@ -2694,21 +2699,21 @@ QPDFWriter::writeLinearized() @@ -2694,21 +2699,21 @@ QPDFWriter::writeLinearized()
2694 qpdf_offset_t second_xref_end = 0; 2699 qpdf_offset_t second_xref_end = 0;
2695 2700
2696 m->next_objid = part4_first_obj; 2701 m->next_objid = part4_first_obj;
2697 - enqueuePart(part4); 2702 + m->enqueuePart(part4);
2698 if (m->next_objid != after_part4) { 2703 if (m->next_objid != after_part4) {
2699 // This can happen with very botched files as in the fuzzer test. There are likely some 2704 // This can happen with very botched files as in the fuzzer test. There are likely some
2700 // faulty assumptions in calculateLinearizationData 2705 // faulty assumptions in calculateLinearizationData
2701 throw std::runtime_error("error encountered after writing part 4 of linearized data"); 2706 throw std::runtime_error("error encountered after writing part 4 of linearized data");
2702 } 2707 }
2703 m->next_objid = part6_first_obj; 2708 m->next_objid = part6_first_obj;
2704 - enqueuePart(part6); 2709 + m->enqueuePart(part6);
2705 if (m->next_objid != after_part6) { 2710 if (m->next_objid != after_part6) {
2706 throw std::runtime_error("error encountered after writing part 6 of linearized data"); 2711 throw std::runtime_error("error encountered after writing part 6 of linearized data");
2707 } 2712 }
2708 m->next_objid = second_half_first_obj; 2713 m->next_objid = second_half_first_obj;
2709 - enqueuePart(part7);  
2710 - enqueuePart(part8);  
2711 - enqueuePart(part9); 2714 + m->enqueuePart(part7);
  2715 + m->enqueuePart(part8);
  2716 + m->enqueuePart(part9);
2712 if (m->next_objid != after_second_half) { 2717 if (m->next_objid != after_second_half) {
2713 throw std::runtime_error("error encountered after writing part 9 of linearized data"); 2718 throw std::runtime_error("error encountered after writing part 9 of linearized data");
2714 } 2719 }
@@ -2947,11 +2952,10 @@ QPDFWriter::writeLinearized() @@ -2947,11 +2952,10 @@ QPDFWriter::writeLinearized()
2947 } 2952 }
2948 2953
2949 void 2954 void
2950 -QPDFWriter::enqueueObjectsStandard() 2955 +QPDFWriter::Members::enqueueObjectsStandard()
2951 { 2956 {
2952 - if (m->preserve_unreferenced_objects) {  
2953 - QTC::TC("qpdf", "QPDFWriter preserve unreferenced standard");  
2954 - for (auto const& oh: m->pdf.getAllObjects()) { 2957 + if (preserve_unreferenced_objects) {
  2958 + for (auto const& oh: pdf.getAllObjects()) {
2955 enqueueObject(oh); 2959 enqueueObject(oh);
2956 } 2960 }
2957 } 2961 }
@@ -2970,14 +2974,14 @@ QPDFWriter::enqueueObjectsStandard() @@ -2970,14 +2974,14 @@ QPDFWriter::enqueueObjectsStandard()
2970 } 2974 }
2971 2975
2972 void 2976 void
2973 -QPDFWriter::enqueueObjectsPCLm() 2977 +QPDFWriter::Members::enqueueObjectsPCLm()
2974 { 2978 {
2975 // Image transform stream content for page strip images. Each of this new stream has to come 2979 // Image transform stream content for page strip images. Each of this new stream has to come
2976 // after every page image strip written in the pclm file. 2980 // after every page image strip written in the pclm file.
2977 std::string image_transform_content = "q /image Do Q\n"; 2981 std::string image_transform_content = "q /image Do Q\n";
2978 2982
2979 // enqueue all pages first 2983 // enqueue all pages first
2980 - std::vector<QPDFObjectHandle> all = m->pdf.getAllPages(); 2984 + std::vector<QPDFObjectHandle> all = pdf.getAllPages();
2981 for (auto& page: all) { 2985 for (auto& page: all) {
2982 // enqueue page 2986 // enqueue page
2983 enqueueObject(page); 2987 enqueueObject(page);
@@ -2990,7 +2994,7 @@ QPDFWriter::enqueueObjectsPCLm() @@ -2990,7 +2994,7 @@ QPDFWriter::enqueueObjectsPCLm()
2990 for (auto& image: strips.as_dictionary()) { 2994 for (auto& image: strips.as_dictionary()) {
2991 if (!image.second.null()) { 2995 if (!image.second.null()) {
2992 enqueueObject(image.second); 2996 enqueueObject(image.second);
2993 - enqueueObject(QPDFObjectHandle::newStream(&m->pdf, image_transform_content)); 2997 + enqueueObject(QPDFObjectHandle::newStream(&pdf, image_transform_content));
2994 } 2998 }
2995 } 2999 }
2996 } 3000 }
@@ -3048,9 +3052,9 @@ QPDFWriter::writeStandard() @@ -3048,9 +3052,9 @@ QPDFWriter::writeStandard()
3048 write(m->extra_header_text); 3052 write(m->extra_header_text);
3049 3053
3050 if (m->pclm) { 3054 if (m->pclm) {
3051 - enqueueObjectsPCLm(); 3055 + m->enqueueObjectsPCLm();
3052 } else { 3056 } else {
3053 - enqueueObjectsStandard(); 3057 + m->enqueueObjectsStandard();
3054 } 3058 }
3055 3059
3056 // Now start walking queue, outputting each object. 3060 // Now start walking queue, outputting each object.
qpdf/qpdf.testcov
@@ -188,7 +188,6 @@ QPDF replace dictionary 0 @@ -188,7 +188,6 @@ QPDF replace dictionary 0
188 QPDF replace stream 0 188 QPDF replace stream 0
189 QPDF replace foreign indirect with null 0 189 QPDF replace foreign indirect with null 0
190 QPDF insert foreign page 0 190 QPDF insert foreign page 0
191 -QPDFWriter foreign object 0  
192 QPDFWriter copy use_aes 1 191 QPDFWriter copy use_aes 1
193 QPDFParser indirect without context 0 192 QPDFParser indirect without context 0
194 QPDFObjectHandle trailing data in parse 0 193 QPDFObjectHandle trailing data in parse 0
@@ -228,7 +227,6 @@ QPDFParser treat word as string in parseRemainder 0 @@ -228,7 +227,6 @@ QPDFParser treat word as string in parseRemainder 0
228 QPDFParser found fake 1 227 QPDFParser found fake 1
229 QPDFParser no val for last key 0 228 QPDFParser no val for last key 0
230 QPDF resolve failure to null 0 229 QPDF resolve failure to null 0
231 -QPDFWriter preserve unreferenced standard 0  
232 QPDFObjectHandle errors in parsecontent 0 230 QPDFObjectHandle errors in parsecontent 0
233 QPDFJob same file error 0 231 QPDFJob same file error 0
234 QPDFJob split-pages %d 0 232 QPDFJob split-pages %d 0
@@ -365,7 +363,6 @@ QPDF_encryption same password 1 @@ -365,7 +363,6 @@ QPDF_encryption same password 1
365 QPDFParser duplicate dict key 0 363 QPDFParser duplicate dict key 0
366 QPDFWriter no encryption sig contents 0 364 QPDFWriter no encryption sig contents 0
367 QPDFPageObjectHelper colorspace lookup 0 365 QPDFPageObjectHelper colorspace lookup 0
368 -QPDFWriter ignore XRef in qdf mode 0  
369 QPDFPageObjectHelper filter form xobject 0 366 QPDFPageObjectHelper filter form xobject 0
370 QPDFJob found resources in non-leaf 0 367 QPDFJob found resources in non-leaf 0
371 QPDFJob found shared resources in leaf 0 368 QPDFJob found shared resources in leaf 0