add doc about zip with optimized metadata

GitOrigin-RevId: 743fc3f1356115308b8936d018f953fb42202000
This commit is contained in:
Vladimir Krivosheev
2023-05-19 12:04:56 +02:00
committed by intellij-monorepo-bot
parent 5128be05e1
commit 305fe4aabf
4 changed files with 301 additions and 23 deletions

View File

@@ -144,10 +144,12 @@ fun transformSvg(svgFile: Path) {
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
@Suppress("HttpUrlsUsage")
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
// so, first node of insertAdjacentHTML result will be svg and not comment
// so, the first node of insertAdjacentHTML result will be svg and not comment
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val fileHeader = "<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->\n".encodeToByteArray()
Files.newOutputStream(svgFile).buffered().use { fileOut ->
fileOut.write(fileHeader)
transformer.transform(DOMSource(document.documentElement), StreamResult(fileOut))
}
}
@@ -192,13 +194,18 @@ fun extractFontStyle(element: Element, classNameToBuilder: MutableMap<String, St
lastUsedMonoFont = fontFamily
}
val className = if (fontFamily != lastUsedMonoFont && size.value == defaultFontSize) {
val className: String
if (fontFamily != lastUsedMonoFont && size.value == defaultFontSize) {
fontFamily = "'Roboto', sans-serif"
"text"
className = "text"
}
else if (size.value == defaultFontSize) {
fontFamily = "'Roboto Mono', monospace"
"code"
className = "code"
}
else if (size.value == "10") {
// legend
return
}
else {
throw UnsupportedOperationException("font combination is unknown (fontFamily=$fontFamily, lastUsedMonoFont=$lastUsedMonoFont)")

99
docs/jar-format.puml Normal file
View File

@@ -0,0 +1,99 @@
@startuml
!include jb-plantuml-theme.puml
skinparam linetype ortho
top to bottom direction
header
A [[https://en.wikipedia.org/wiki/ZIP_(file_format) ZIP]] file format with optimized metadata.
endheader
component "File Entry 1" as FE1
component "File Entry N" as FE2
note right of FE1
The relative offset of the local file header does not point directly to the data,
but rather to the header itself. This means that you need to perform two seeks
in order to locate the actual data, as the size of the local file header can vary.
As an optimization, you can attempt to precompute the data offset
when reading the central directory file header.
This optimization is implemented in the HashMapZipFile class.
However, ImmutableZipFile uses a special index for this purpose, as explained below.
end note
FE1 -- FE2
component "File entry ~__index__" as INDEX {
component "A list of keys along with their corresponding offsets and sizes." as INDEX_M
note right of INDEX_M
A list of pairs consisting of long values.
Each pair includes a key, represented as a 64-bit XXH3 hash of an entry name,
and an offset and size represented as two ints packed into a single long value.
This list enables the retrieval of data locations for all entries in a single bulk read operation.
It contains no file names or other unnecessary metadata.
end note
component "class package hashes" as INDEX_PC
note right of INDEX_PC
A list of long values representing the 64-bit XXH3 hash of a package name.
This list is not used by the ZipFile implementation but is consumed by the class loader.
It allows for a quick determination of whether a class name is located within a ZIP file or not.
While it does not provide much benefit for a single ZIP file, as name lookup can be done with a single map lookup,
it enables the clustering of multiple ZIP files.
This clustering helps avoid a linear search across all ZIP files in a classpath.
end note
component "resource package hashes" as INDEX_PR
note right of INDEX_PR
The same concept applies to resource package hashes.
However, there are two different sets of hashes since there is no correlation
between class packages and resource packages.
end note
component names {
component "name lengths" as INDEX_NL
note right of INDEX_NL
A list of name lengths represented as shorts.
This list enables the reading of integers in a single bulk read operation,
directly from native memory.
end note
component "names" as INDEX_NS
note right of INDEX_NS
List of strings.
end note
INDEX_NL -down- INDEX_NS
}
note bottom of names
Entry names. They are not loaded into memory when the ZipFile is opened;
instead, they are loaded only when requested.
This is useful, for instance, when you want to process entries based on their names,
such as finding entries by a specific prefix.
end note
INDEX_M -- INDEX_PC
INDEX_PC -- INDEX_PR
INDEX_PR -- names
}
note top of INDEX
The Zip specification is not violated.
The index data represents a regular file entry.
end note
FE2 -- INDEX
component "Central directory" as CD
note right of CD
The index format version is stored in the 'File comment' field.
Only the latest format is supported.
If a ZIP file does not have a comment or the index version is not equal to the latest,
a fallback implementation is used that is capable of reading any ZIP file.
end note
INDEX -- CD
@enduml

View File

@@ -4,8 +4,8 @@
<meta charset="utf-8">
<title>IntelliJ Platform Activity Diagrams</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css" integrity="sha256-O8SsQwDg1R10WnKJNyYgd9J3rlom+YSVcGbEF5RmfFk=" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.4.0/dist/panzoom.min.js" integrity="sha256-+djd9iOn6UHGwEA/IKKJL4nUsY9LaSoc8YYtbFhZSPw=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" integrity="sha256-rTpdO0HXBCNpreAHcu6tB2Ppg515Vo+5GtYSsnNLz+8=" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js" integrity="sha256-9PI4Hcqdhk6UDuIArOUvzRL9S/uJRmDpkA2o4gIigDs=" crossorigin="anonymous"></script>
</head>
<body>
<script>
@@ -33,14 +33,7 @@
}
parent.insertAdjacentHTML("afterbegin", svgContent)
panzoom = Panzoom(parent.firstChild, {
disableZoom: true,
handleStartEvent: (event) => {
event.stopPropagation()
// otherwise, text is not selectable (at least, works in Safari, doesn't work in Chrome in any case)
// event.preventDefault()
},
})
panzoom = Panzoom(parent)
})
}
@@ -49,15 +42,14 @@
}
</script>
<section class="section">
<div class="select is-pulled-right panzoom-exclude">
<label>
<select id="diagramSelector" onchange="diagramChanged(event)">
<option value="getting-service">Getting Service</option>
<option value="projectClose-dispose-flow">Close Project Flow</option>
<option value="icon-loading-stat">Icon Loading Stats</option>
<option value="plugin-model">Plugin Model V2</option>
</select>
</label>
<div id="diagramSelectorDiv" class="select is-pulled-right panzoom-exclude" style="z-index: 100;">
<select id="diagramSelector" onchange="diagramChanged(event)">
<option value="getting-service">Getting Service</option>
<option value="projectClose-dispose-flow">Close Project Flow</option>
<option value="icon-loading-stat">Icon Loading Stats</option>
<option value="plugin-model">Plugin Model V2</option>
<option value="jar-format">Optimized ZIP</option>
</select>
</div>
<!--suppress CheckTagEmptyBody -->
<div id="svgContainer" style="width: 100%; height: 100%;">

180
docs/out/jar-format.svg Normal file
View File

@@ -0,0 +1,180 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="1030px" preserveAspectRatio="none" style="width:2207px;height:1030px;background:#FFFFFF;" version="1.1" viewBox="0 0 2207 1030" width="2207px" zoomAndPan="magnify">
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono&amp;display=swap');
</style>
<defs>
<style type="text/css">@import url('https://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900,900italic');</style>
<filter height="300%" id="f1aayaydvrt5fz" width="300%" x="-1" y="-1">
<feGaussianBlur result="blurOut" stdDeviation="2.0"/>
<feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/>
<feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/>
<feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/>
</filter>
</defs>
<g>
<text fill="#888888" font-family="Roboto" font-size="10" textLength="7" x="992" y="9.668">A</text>
<a href="https://en.wikipedia.org/wiki/ZIP_(file_format)" target="_top" title="https://en.wikipedia.org/wiki/ZIP_(file_format)" xlink:actuate="onRequest" xlink:href="https://en.wikipedia.org/wiki/ZIP_(file_format)" xlink:show="new" xlink:title="https://en.wikipedia.org/wiki/ZIP_(file_format)" xlink:type="simple">
<text fill="#1D1D1D" font-family="Roboto" font-size="10" lengthAdjust="spacing" text-decoration="underline" textLength="15" x="1002" y="9.668">ZIP</text>
</a>
<text fill="#888888" font-family="Roboto" font-size="10" textLength="181" x="1020" y="9.668">file format with optimized metadata.</text>
<!--cluster INDEX-->
<g id="cluster_INDEX">
<rect fill="none" filter="url(#f1aayaydvrt5fz)" height="730.52" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:1.0;" width="1412" x="7" y="287.6573"/>
<text fill="#000000" font-family="Roboto" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="140" x="643" y="303.1925">File entry __index__</text>
</g>
<!--cluster names-->
<g id="cluster_names">
<rect fill="none" filter="url(#f1aayaydvrt5fz)" height="242.69" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:1.0;" width="724" x="47" y="735.4873"/>
<text fill="#000000" font-family="Roboto" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="47" x="385.5" y="751.0225">names</text>
</g>
<!--entity INDEX_M-->
<g id="elem_INDEX_M">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="445" x="246.5" y="358.6373"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="425" x="256.5" y="382.1725">A list of keys along with their corresponding offsets and sizes.</text>
</g>
<g id="elem_GMN10">
<path d="M727,330.6573 L727,373.1773 L691.94,377.1773 L727,381.1773 L727,423.0988 A0,0 0 0 0 727,423.0988 L1375,423.0988 A0,0 0 0 0 1375,423.0988 L1375,340.6573 L1365,330.6573 L727,330.6573 A0,0 0 0 0 727,330.6573 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1365,330.6573 L1365,340.6573 L1375,340.6573 L1365,330.6573 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="268" x="733" y="349.1925">A list of pairs consisting of long values.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="535" x="733" y="365.6808">Each pair includes a key, represented as a 64-bit XXH3 hash of an entry name,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="535" x="733" y="382.1691">and an offset and size represented as two ints packed into a single long value.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="627" x="733" y="398.6573">This list enables the retrieval of data locations for all entries in a single bulk read operation.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="388" x="733" y="415.1456">It contains no file names or other unnecessary metadata.</text>
</g>
<!--entity INDEX_PC-->
<g id="elem_INDEX_PC">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="166" x="386" y="519.3173"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="146" x="396" y="542.8525">class package hashes</text>
</g>
<g id="elem_GMN14">
<path d="M587,483.0973 L587,534.1773 L552.23,538.1773 L587,542.1773 L587,592.027 A0,0 0 0 0 587,592.027 L1387,592.027 A0,0 0 0 0 1387,592.027 L1387,493.0973 L1377,483.0973 L587,483.0973 A0,0 0 0 0 587,483.0973 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1377,483.0973 L1377,493.0973 L1387,493.0973 L1377,483.0973 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="515" x="593" y="501.6325">A list of long values representing the 64-bit XXH3 hash of a package name.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="591" x="593" y="518.1208">This list is not used by the ZipFile implementation but is consumed by the class loader.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="629" x="593" y="534.6091">It allows for a quick determination of whether a class name is located within a ZIP file or not.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="779" x="593" y="551.0973">While it does not provide much benefit for a single ZIP file, as name lookup can be done with a single map lookup,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="302" x="593" y="567.5856">it enables the clustering of multiple ZIP files.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="512" x="593" y="584.0739">This clustering helps avoid a linear search across all ZIP files in a classpath.</text>
</g>
<!--entity INDEX_PR-->
<g id="elem_INDEX_PR">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="193" x="496.5" y="663.5173"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="173" x="506.5" y="687.0525">resource package hashes</text>
</g>
<g id="elem_GMN18">
<path d="M725,652.0273 L725,678.1773 L689.82,682.1773 L725,686.1773 L725,711.4922 A0,0 0 0 0 725,711.4922 L1263,711.4922 A0,0 0 0 0 1263,711.4922 L1263,662.0273 L1253,652.0273 L725,652.0273 A0,0 0 0 0 725,652.0273 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1253,652.0273 L1253,662.0273 L1263,662.0273 L1253,652.0273 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="376" x="731" y="670.5625">The same concept applies to resource package hashes.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="517" x="731" y="687.0508">However, there are two different sets of hashes since there is no correlation</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="329" x="731" y="703.5391">between class packages and resource packages.</text>
</g>
<g id="elem_GMN31">
<path d="M794.5,897.9573 L794.5,973.9105 L1387.5,973.9105 L1387.5,907.9573 L1377.5,897.9573 L794.5,897.9573 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1377.5,897.9573 L1377.5,907.9573 L1387.5,907.9573 L1377.5,897.9573 " fill="#FAFAFA" style="stroke:#181818;stroke-width:1.0;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="511" x="800.5" y="916.4925">Entry names. They are not loaded into memory when the ZipFile is opened;</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="315" x="800.5" y="932.9808">instead, they are loaded only when requested.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="572" x="800.5" y="949.4691">This is useful, for instance, when you want to process entries based on their names,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="291" x="800.5" y="965.9573">such as finding entries by a specific prefix.</text>
</g>
<!--entity INDEX_NL-->
<g id="elem_INDEX_NL">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="113" x="70.5" y="789.9773"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="93" x="80.5" y="813.5125">name lengths</text>
</g>
<g id="elem_GMN23">
<path d="M218,778.4873 L218,805.1773 L183.75,809.1773 L218,813.1773 L218,837.9522 A0,0 0 0 0 218,837.9522 L728,837.9522 A0,0 0 0 0 728,837.9522 L728,788.4873 L718,778.4873 L218,778.4873 A0,0 0 0 0 218,778.4873 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M718,778.4873 L718,788.4873 L728,788.4873 L718,778.4873 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="306" x="224" y="797.0225">A list of name lengths represented as shorts.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="489" x="224" y="813.5108">This list enables the reading of integers in a single bulk read operation,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="196" x="224" y="829.9991">directly from native memory.</text>
</g>
<!--entity INDEX_NS-->
<g id="elem_INDEX_NS">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="65" x="94.5" y="917.6873"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="45" x="104.5" y="941.2225">names</text>
</g>
<g id="elem_GMN27">
<path d="M194.5,922.6873 L194.5,932.1773 L159.78,936.1773 L194.5,940.1773 L194.5,949.1756 A0,0 0 0 0 194.5,949.1756 L311.5,949.1756 A0,0 0 0 0 311.5,949.1756 L311.5,932.6873 L301.5,922.6873 L194.5,922.6873 A0,0 0 0 0 194.5,922.6873 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M301.5,922.6873 L301.5,932.6873 L311.5,932.6873 L301.5,922.6873 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="96" x="200.5" y="941.2225">List of strings.</text>
</g>
<!--entity FE1-->
<g id="elem_FE1">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="96" x="1238" y="71.4773"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="76" x="1248" y="95.0125">File Entry 1</text>
</g>
<!--entity FE2-->
<g id="elem_FE2">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="97" x="1237.5" y="223.9273"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="77" x="1247.5" y="247.4625">File Entry N</text>
</g>
<g id="elem_GMN4">
<path d="M1368.5,18.7773 L1368.5,86.1773 L1334.37,90.1773 L1368.5,94.1773 L1368.5,160.6836 A0,0 0 0 0 1368.5,160.6836 L1973.5,160.6836 A0,0 0 0 0 1973.5,160.6836 L1973.5,28.7773 L1963.5,18.7773 L1368.5,18.7773 A0,0 0 0 0 1368.5,18.7773 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1963.5,18.7773 L1963.5,28.7773 L1973.5,28.7773 L1963.5,18.7773 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="520" x="1374.5" y="37.3125">The relative offset of the local file header does not point directly to the data,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="535" x="1374.5" y="53.8008">but rather to the header itself. This means that you need to perform two seeks</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="524" x="1374.5" y="70.2891">in order to locate the actual data, as the size of the local file header can vary.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="4" x="1374.5" y="86.7773"> </text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="455" x="1374.5" y="103.2656">As an optimization, you can attempt to precompute the data offset</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="317" x="1374.5" y="119.7539">when reading the central directory file header.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="425" x="1374.5" y="136.2422">This optimization is implemented in the HashMapZipFile class.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="584" x="1374.5" y="152.7305">However, ImmutableZipFile uses a special index for this purpose, as explained below.</text>
</g>
<g id="elem_GMN37">
<path d="M1369.5,220.6773 L1369.5,263.6539 L1704.5,263.6539 L1704.5,230.6773 L1694.5,220.6773 L1369.5,220.6773 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M1694.5,220.6773 L1694.5,230.6773 L1704.5,230.6773 L1694.5,220.6773 " fill="#FAFAFA" style="stroke:#181818;stroke-width:1.0;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="245" x="1375.5" y="239.2125">The Zip specification is not violated.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="314" x="1375.5" y="255.7008">The index data represents a regular file entry.</text>
</g>
<!--entity CD-->
<g id="elem_CD">
<rect fill="#F1F1F1" filter="url(#f1aayaydvrt5fz)" height="36.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="135" x="1443.5" y="519.3173"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="115" x="1453.5" y="542.8525">Central directory</text>
</g>
<g id="elem_GMN42">
<path d="M1614,499.5873 L1614,534.1773 L1578.62,538.1773 L1614,542.1773 L1614,575.5405 A0,0 0 0 0 1614,575.5405 L2200,575.5405 A0,0 0 0 0 2200,575.5405 L2200,509.5873 L2190,499.5873 L1614,499.5873 A0,0 0 0 0 1614,499.5873 " fill="#FAFAFA" filter="url(#f1aayaydvrt5fz)" style="stroke:#181818;stroke-width:0.5;"/>
<path d="M2190,499.5873 L2190,509.5873 L2200,509.5873 L2190,499.5873 " fill="#FAFAFA" style="stroke:#181818;stroke-width:0.5;"/>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="419" x="1620" y="518.1225">The index format version is stored in the 'File comment' field.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="242" x="1620" y="534.6108">Only the latest format is supported.</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="565" x="1620" y="551.0991">If a ZIP file does not have a comment or the index version is not equal to the latest,</text>
<text fill="#000000" font-family="Roboto" font-size="14" lengthAdjust="spacing" textLength="488" x="1620" y="567.5873">a fallback implementation is used that is capable of reading any ZIP file.</text>
</g>
<!--link FE1 to FE2-->
<g id="link_FE1_FE2">
<path d="M1286,108.4173 C1286,137.8373 1286,194.5873 1286,223.7973 " fill="none" id="FE1-FE2" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link INDEX_NL to INDEX_NS-->
<g id="link_INDEX_NL_INDEX_NS">
<path d="M127,826.7973 C127,851.0073 127,893.1173 127,917.3373 " fill="none" id="INDEX_NL-INDEX_NS" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link names to GMN31-->
<g id="link_names_GMN31">
<path d="M771.1021,908.1773 C771.185,908.1773 771.2684,908.1773 771.3525,908.1773 C776.7325,908.1773 784.52,908.1773 794.15,908.1773 " fill="none" id="names-GMN31" style="stroke:#181818;stroke-width:1.0;stroke-dasharray:7.0,7.0;"/>
</g>
<!--link INDEX_M to INDEX_PC-->
<g id="link_INDEX_M_INDEX_PC">
<path d="M469,395.3073 C469,426.1373 469,487.8373 469,518.8473 " fill="none" id="INDEX_M-INDEX_PC" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link INDEX_PC to INDEX_PR-->
<g id="link_INDEX_PC_INDEX_PR">
<path d="M524.25,556.1173 C524.25,583.8073 524.25,635.6273 524.25,663.2773 " fill="none" id="INDEX_PC-INDEX_PR" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link INDEX_PR to names-->
<g id="link_INDEX_PR_names">
<path d="M593,700.4473 C593,709.7973 593,720.9798 593,729.8248 C593,730.9305 593,731.9996 593,733.024 C593,733.5362 593,734.0373 593,734.5261 C593,734.7706 593,735.012 593,735.2502 C593,735.3097 593,735.3691 593,735.4282 " fill="none" id="INDEX_PR-names" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link GMN37 to INDEX-->
<g id="link_GMN37_INDEX">
<path d="M1398.75,263.8573 C1398.75,268.6286 1398.75,273.9777 1398.75,279.6855 C1398.75,281.1125 1398.75,282.5619 1398.75,284.0303 C1398.75,284.7645 1398.75,285.5034 1398.75,286.2467 C1398.75,286.6183 1398.75,286.9911 1398.75,287.3648 " fill="none" id="GMN37-INDEX" style="stroke:#181818;stroke-width:1.0;stroke-dasharray:7.0,7.0;"/>
</g>
<!--link FE2 to INDEX-->
<g id="link_FE2_INDEX">
<path d="M1286,260.7873 C1286,268.7673 1286,277.8648 1286,284.9673 C1286,285.8552 1286,286.7118 1286,287.5312 " fill="none" id="FE2-INDEX" style="stroke:#181818;stroke-width:1.0;"/>
</g>
<!--link INDEX to CD-->
<g id="link_INDEX_CD">
<path d="M1419.0864,538.1773 C1419.1718,538.1773 1419.2577,538.1773 1419.3442,538.1773 C1419.6901,538.1773 1420.0451,538.1773 1420.4088,538.1773 C1426.2275,538.1773 1434.275,538.1773 1443.19,538.1773 " fill="none" id="INDEX-CD" style="stroke:#181818;stroke-width:1.0;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB