merge with John's branch

This commit is contained in:
Tomasz Długosz 2011-08-25 21:55:12 +02:00
commit 3d9627b33e
226 changed files with 84819 additions and 157514 deletions

View File

@ -19,6 +19,106 @@
# new recipes:
# - title:
- version: 0.8.14
date: 2011-08-12
new features:
- title: "Make the keyboard shortcuts used by the main calibre interface user customizable, via Preferences->Advanced->Keyboard"
type: major
- title: "When switching libraries, if the library no longer exists, give the user a chance to specify a new location for the library, in case it was moved, before forgetting it."
tickets: [822018]
- title: "Template language: Add strcat and strlen builtin functions."
tickets: [821935]
bug fixes:
- title: "The various options to control how automerging works when adding books now also apply when copying a book from one library to another."
tickets: [822033]
- title: "Ebook viewer: Respond to key presses even when the book display area does not have keyboard focus"
- title: "Allow integer and float column values to go to -999999. -1000000 is the value of 'undefined'."
tickets: [821941]
- title: "Fix in calibre browser not working for the Open books store in Get Books."
tickets: [822359]
- title: "Fix regression in 0.8.13 that caused incorrect title/author for downloaded news if you turned off reading metadata from file contents in Preferences->Adding books"
- title: "Save to disk: When saving to a single directory, handle the case of the save to disk template containing path separators inside template expression correctly."
tickets: [821912]
- title: "Get Books: Always read metadata from the file contents, ignoring the setting in Preferences->Adding books"
- title: "Fix merge_metadata to not overwrite non-text fields ('bool', 'int', 'float', 'rating', 'datetime') that have a value of zero/false instead of None."
tickets: [821665]
improved recipes:
- The Independent
new recipes:
- title: "Novinite"
author: Martin Tsanchev
- title: "Blog Escrevinhador"
author: Diniz Bortolotto
- version: 0.8.13
date: 2011-08-05
new features:
- title: "Add a new action 'Pick Random Book' that can be added to the toolbar via Preferences->Toolbars."
tickets: [818315]
- title: "Driver for Droid X2"
tickets: [821053]
- title: "PDF metadata: Support reading/writing of tags from the Keywords field in PDF files."
- title: "MOBI Input: Speedup reading of HUFF/CDIC compressed files"
- title: "MOBI Output: Add a command line option --extract-to that uses the inspect MOBI tool to extract the created MOBI file to the specified directory"
- title: "Template language: Add a few new functions to manipulate lists (list_difference, list_intersection, list_sort)"
- title: "Make the Manage Tags/Publishers/etc. dialog show a column with counts for each item, to easily sort by number of items"
- title: "MOBI Output: Generate navpoints for items at every level in the TOC, not just the deepest level"
bug fixes:
- title: "MOBI Output: Remove option to choose masthead font as the font selection control causes crashes on some windows systems"
- title: "MOBI Output: Fix bug that caused paragraphs that had only a non breaking space as text before the first child element to be removed."
tickets: [819058]
- title: "Display undefined dates properly in the Book details panel."
tickets: [819222]
- title: "Fix regression that broke deleting of books from first generation Kobos with un-upgraded firmware"
tickets: [818704]
- title: "Get books: Fix Gutenberg store and improvements to chitanka.info and e-knigni.net"
- title: "News download: Support https proxies"
- title: "Check library did not know about original_* files"
- title: "Fix crash caused by having very large numbers of authors > 100 for a book"
improved recipes:
- Nikkei News
new recipes:
- title: Carta Capital
author: Pablo Aldama
- title: El Tiempo, El Colombiano and Portafolio Colombia
author: Cavalencia
- version: 0.8.12
date: 2011-07-29
@ -198,8 +298,8 @@
- title: Techcrunch and Pecat
author: Darko Miletic
- title: Vio Mundo, IDG Now and Tojolaco
author: Diniz Bortoletto
- title: "Vio Mundo, IDG Now! and Tojolaco"
author: Diniz Bortolotto
- title: Geek and Poke, Automatiseringgids IT
author: DrMerry

912
imgsrc/keyboard-prefs.svg Normal file
View File

@ -0,0 +1,912 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="Livello_1"
width="128"
height="128"
viewBox="0 0 144 94"
overflow="visible"
enable-background="new 0 0 144 94"
xml:space="preserve"
sodipodi:version="0.32"
inkscape:version="0.45+devel"
sodipodi:docname="preferences-desktop-keyboard.svgz"
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
style="overflow:visible"><metadata
id="metadata224"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs222"><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_35_"
id="linearGradient2719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
x1="72.000504"
y1="83.799797"
x2="72.000504"
y2="5.8003001" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2721"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-255.92554,-542.49842)"
x1="780.77576"
y1="-1248.1824"
x2="780.81049"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2723"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-263.1733,-542.49842)"
x1="708.36438"
y1="-1248.1824"
x2="708.39648"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2725"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-270.42218,-542.49842)"
x1="635.95538"
y1="-1248.1824"
x2="635.9834"
y2="-1195.5962" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2727"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-253.92268,-535.12325)"
x1="790.77502"
y1="-1324.245"
x2="790.81049"
y2="-1271.6509" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2729"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-261.17157,-535.12325)"
x1="718.36609"
y1="-1324.245"
x2="718.39838"
y2="-1271.6509" /><linearGradient
inkscape:collect="always"
xlink:href="#rect3941_1_"
id="linearGradient2731"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.474754,0,0,-0.465075,-268.41933,-535.12325)"
x1="645.95471"
y1="-1324.245"
x2="645.9834"
y2="-1271.6509" /><filter
inkscape:collect="always"
x="-0.36659993"
width="1.7331999"
y="-0.17839379"
height="1.3567876"
id="filter3416"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.51430916"
id="feGaussianBlur3418" /></filter><filter
inkscape:collect="always"
x="-0.36972603"
width="1.7394521"
y="-0.17766281"
height="1.3553256"
id="filter3424"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.51984026"
id="feGaussianBlur3426" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3444"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3446" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3448"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3450" /></filter><filter
inkscape:collect="always"
x="-0.22183562"
width="1.4436712"
y="-0.10659768"
height="1.2131954"
id="filter3452"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31190415"
id="feGaussianBlur3454" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3456"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3458" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3460"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3462" /></filter><filter
inkscape:collect="always"
x="-0.21991603"
width="1.4398321"
y="-0.10704668"
height="1.2140934"
id="filter3464"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30861549"
id="feGaussianBlur3466" /></filter><filter
inkscape:collect="always"
x="-0.22179123"
width="1.4435825"
y="-0.10660794"
height="1.2132159"
id="filter3468"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31193415"
id="feGaussianBlur3470" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3472"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3474" /></filter><filter
inkscape:collect="always"
x="-0.22183562"
width="1.4436712"
y="-0.10659768"
height="1.2131954"
id="filter3476"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.31190415"
id="feGaussianBlur3478" /></filter><filter
inkscape:collect="always"
x="-0.21995996"
width="1.4399199"
y="-0.10703628"
height="1.2140726"
id="filter3484"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.30858549"
id="feGaussianBlur3486" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3492"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3494" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3496"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3498" /></filter><filter
inkscape:collect="always"
x="-0.028919654"
width="1.0578393"
y="-0.14108369"
height="1.2821674"
id="filter3500"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33592005"
id="feGaussianBlur3502" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3504"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3506" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3508"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3510" /></filter><filter
inkscape:collect="always"
x="-0.02891983"
width="1.0578397"
y="-0.14107949"
height="1.282159"
id="filter3512"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.33591005"
id="feGaussianBlur3514" /></filter><linearGradient
id="XMLID_31_"
gradientUnits="userSpaceOnUse"
x1="69.333504"
y1="17.6504"
x2="69.333504"
y2="9.7958002"
xlink:href="#XMLID_32_">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop169" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop171" />
</linearGradient><linearGradient
id="XMLID_32_"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop186" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop188" />
</linearGradient><linearGradient
id="XMLID_30_"
gradientUnits="userSpaceOnUse"
x1="31.742201"
y1="17.6504"
x2="31.742201"
y2="9.7958002">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop152" /><stop
id="stop3366"
style="stop-color:#eaeaea;stop-opacity:1;"
offset="0.68235296" />
<stop
offset="1"
style="stop-color:#c8c8c8;stop-opacity:1;"
id="stop154" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2945"
gradientUnits="userSpaceOnUse"
x1="69.333504"
y1="17.6504"
x2="69.333504"
y2="9.7958002" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2947"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002" /><linearGradient
xlink:href="#XMLID_30_"
id="XMLID_28_"
gradientUnits="userSpaceOnUse"
x1="38.033699"
y1="55.649399"
x2="38.033699"
y2="47.795502"
spreadMethod="pad">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop118" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop120" />
</linearGradient><linearGradient
id="XMLID_29_"
gradientUnits="userSpaceOnUse"
x1="75.333"
y1="55.649399"
x2="75.333"
y2="47.795502">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop135" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop137" />
</linearGradient><linearGradient
id="XMLID_33_"
gradientUnits="userSpaceOnUse"
x1="112.334"
y1="55.649399"
x2="112.334"
y2="47.795502">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop203" />
<stop
offset="1"
style="stop-color:#DDDDDD"
id="stop205" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2979"
gradientUnits="userSpaceOnUse"
spreadMethod="pad"
x1="38.033699"
y1="55.649399"
x2="38.033699"
y2="47.795502" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2981"
gradientUnits="userSpaceOnUse"
x1="75.333"
y1="55.649399"
x2="75.333"
y2="47.795502" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient2983"
gradientUnits="userSpaceOnUse"
x1="112.334"
y1="55.649399"
x2="112.334"
y2="47.795502" /><filter
inkscape:collect="always"
id="filter3372"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.3179705"
id="feGaussianBlur3374" /></filter><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient3378"
gradientUnits="userSpaceOnUse"
x1="106.334"
y1="17.6504"
x2="106.334"
y2="9.7958002" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_30_"
id="linearGradient3380"
gradientUnits="userSpaceOnUse"
x1="31.742201"
y1="17.6504"
x2="31.742201"
y2="9.7958002" /></defs><sodipodi:namedview
inkscape:window-height="696"
inkscape:window-width="998"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
height="128px"
width="128px"
inkscape:zoom="2.8284271"
inkscape:cx="65.761733"
inkscape:cy="68.182683"
inkscape:window-x="26"
inkscape:window-y="0"
inkscape:current-layer="g2620" />
<filter
id="AI_Sfocatura_2">
<feGaussianBlur
stdDeviation="2"
id="feGaussianBlur4" />
</filter>
<path
display="none"
d="M 89.758,1.8 C 88.983,1.8 88.229,1.945 87.501,2.17 C 86.774,1.945 86.022,1.8 85.248,1.8 L 52.752,1.8 C 51.978,1.8 51.226,1.945 50.499,2.17 C 49.772,1.944 49.02,1.8 48.245,1.8 L 15.755,1.8 C 11.479,1.8 8,5.275 8,9.546 L 8,42.054 C 8,45.72 10.57,48.783 14,49.582 C 14,53.16 14,80.057 14,80.057 C 14,84.327 17.478,87.8 21.752,87.8 L 54.248,87.8 C 55.022,87.8 55.774,87.655 56.5,87.431 C 57.227,87.656 57.979,87.8 58.754,87.8 L 91.241,87.8 C 92.017,87.8 92.77,87.655 93.498,87.431 C 94.225,87.655 94.977,87.8 95.751,87.8 L 128.241,87.8 C 132.518,87.8 135.999,84.326 135.999,80.057 L 135.999,47.546 C 135.999,43.881 133.43,40.818 129.999,40.019 C 129.999,36.442 129.999,9.546 129.999,9.546 C 129.999,5.275 126.521,1.8 122.247,1.8 L 89.758,1.8 L 89.758,1.8 z"
id="path6"
style="fill:#ff00bf;display:none" />
<linearGradient
id="XMLID_35_"
gradientUnits="userSpaceOnUse"
x1="72.000504"
y1="83.799797"
x2="72.000504"
y2="5.8003001">
<stop
offset="0.0059"
style="stop-color:#888888"
id="stop13" />
<stop
offset="0.5"
style="stop-color:#555555"
id="stop15" />
<stop
offset="0.54"
style="stop-color:#888888"
id="stop17" />
<stop
offset="1"
style="stop-color:#555555"
id="stop19" />
</linearGradient>
<linearGradient
id="rect3785_1_"
gradientUnits="userSpaceOnUse"
x1="780.81049"
y1="-1240.9404"
x2="780.81049"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-223.4874,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop24" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop26" />
</linearGradient>
<linearGradient
id="rect3791_1_"
gradientUnits="userSpaceOnUse"
x1="708.39648"
y1="-1240.9404"
x2="708.39648"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-229.9298,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop38" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop40" />
</linearGradient>
<linearGradient
id="rect3797_1_"
gradientUnits="userSpaceOnUse"
x1="635.9834"
y1="-1240.9404"
x2="635.9834"
y2="-1195.5962"
gradientTransform="matrix(0.422,0,0,-0.4134,-236.3732,-472.1986)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop52" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop54" />
</linearGradient>
<linearGradient
id="rect3929_1_"
gradientUnits="userSpaceOnUse"
x1="790.81049"
y1="-1316.9951"
x2="790.81049"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-221.7071,-465.6429)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop66" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop68" />
</linearGradient>
<linearGradient
id="rect3935_1_"
gradientUnits="userSpaceOnUse"
x1="718.39838"
y1="-1316.9951"
x2="718.39838"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-228.1505,-465.6429)">
<stop
offset="0"
style="stop-color:#BEBEBE"
id="stop80" />
<stop
offset="1"
style="stop-color:#EDEDED"
id="stop82" />
</linearGradient>
<linearGradient
id="rect3941_1_"
gradientUnits="userSpaceOnUse"
x1="645.9834"
y1="-1316.9951"
x2="645.9834"
y2="-1271.6509"
gradientTransform="matrix(0.422,0,0,-0.4134,-234.5929,-465.6429)">
<stop
offset="0"
style="stop-color:#e4e4e4;stop-opacity:1;"
id="stop94" /><stop
id="stop3516"
style="stop-color:#bebebe;stop-opacity:1;"
offset="0.18012393" /><stop
offset="0.61417598"
style="stop-color:#cdcdcd;stop-opacity:1;"
id="stop3376" />
<stop
offset="1"
style="stop-color:#b3b3b3;stop-opacity:1;"
id="stop96" />
</linearGradient>
<g
id="g8"
style="opacity:0.8;filter:url(#AI_Sfocatura_2)"
transform="matrix(1.1250094,0,0,1.125,-9.0001128,-12.4)">
<path
d="M 128.242,45.8 L 125.547,45.8 C 125.826,45.276 126,44.688 126,44.054 L 126,11.546 C 126,9.481 124.316,7.8 122.248,7.8 L 89.758,7.8 C 88.907,7.8 88.131,8.093 87.501,8.57 C 86.872,8.093 86.097,7.8 85.248,7.8 L 52.752,7.8 C 51.903,7.8 51.128,8.093 50.499,8.57 C 49.869,8.094 49.094,7.8 48.245,7.8 L 15.755,7.8 C 13.685,7.8 12,9.48 12,11.546 L 12,44.054 C 12,46.119 13.685,47.8 15.755,47.8 L 18.453,47.8 C 18.173,48.323 18,48.912 18,49.546 L 18,82.057 C 18,84.121 19.683,85.8 21.752,85.8 L 54.248,85.8 C 55.097,85.8 55.872,85.507 56.5,85.031 C 57.13,85.507 57.905,85.8 58.754,85.8 L 91.241,85.8 C 92.092,85.8 92.868,85.507 93.498,85.03 C 94.127,85.507 94.902,85.8 95.751,85.8 L 128.241,85.8 C 130.313,85.8 131.999,84.121 131.999,82.057 L 131.999,49.546 C 132,47.48 130.314,45.8 128.242,45.8 z"
id="path10"
style="opacity:0.8" />
</g><g
id="g2620"
transform="translate(-4.5,0)"><path
style="fill:url(#linearGradient2721);fill-opacity:1"
d="M 96.478481,-2.5 L 133.03004,-2.5 C 134.11905,-2.5 135.00105,-1.621375 135.00105,-0.53575 L 135.00105,36.03575 C 135.00105,37.121374 134.11905,38 133.03004,38 L 96.478481,38 C 95.382722,38 94.500715,37.121374 94.500715,36.03575 L 94.500715,-0.53575 C 94.500715,-1.621375 95.382722,-2.5 96.478481,-2.5 z"
id="rect3785" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3787">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-240.4975,-567.9111)"
r="40.036301"
cy="-971.75677"
cx="527.62299"
id="XMLID_17_">
<stop
id="stop31"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop33"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_17_)"
id="path35"
d="M 91.145,9.856 L 89.924,35.848 L 89.852,37.041 C 91.107,37.752 92.344,38.38 93.661,38.908 C 93.683,38.914 93.717,38.9 93.738,38.908 C 97.663,40.465 101.819,41.179 105.966,41.145 C 106.662,41.14 107.359,41.12 108.055,41.072 C 109.436,40.973 110.852,40.816 112.223,40.55 C 113.593,40.285 114.914,39.932 116.252,39.504 C 118.259,38.861 120.26,38.043 122.15,37.042 L 122.077,35.849 L 120.928,9.857 L 91.145,9.857 L 91.145,9.856 z" />
</g><path
style="fill:url(#linearGradient2723);fill-opacity:1"
d="M 54.846383,-2.5 L 91.404689,-2.5 C 92.492573,-2.5 93.375705,-1.621375 93.375705,-0.53575 L 93.375705,36.03575 C 93.375705,37.121374 92.492573,38 91.404689,38 L 54.846383,38 C 53.758499,38 52.875367,37.121374 52.875367,36.03575 L 52.875367,-0.53575 C 52.875367,-1.621375 53.758499,-2.5 54.846383,-2.5 z"
id="rect3791" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3793">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-247.2058,-567.9111)"
r="40.036098"
cy="-971.12653"
cx="480.29791"
id="XMLID_19_">
<stop
id="stop45"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop47"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_19_)"
id="path49"
d="M 54.145,9.856 L 52.921,35.848 L 52.849,37.041 C 54.109,37.752 55.348,38.38 56.663,38.908 C 56.682,38.914 56.715,38.9 56.734,38.908 C 60.662,40.465 64.821,41.179 68.962,41.145 C 69.658,41.14 70.355,41.12 71.048,41.072 C 72.433,40.973 73.848,40.816 75.218,40.55 C 76.589,40.285 77.909,39.932 79.253,39.504 C 81.254,38.861 83.255,38.043 85.147,37.042 L 85.08,35.849 L 83.927,9.856 L 54.145,9.856 z" />
</g><path
style="fill:url(#linearGradient2725);fill-opacity:1"
d="M 13.224411,-2.5 L 49.775966,-2.5 C 50.867225,-2.5 51.750357,-1.621375 51.750357,-0.53575 L 51.750357,36.03575 C 51.750357,37.121374 50.867225,38 49.775966,38 L 13.224411,38 C 12.133151,38 11.250019,37.121374 11.250019,36.03575 L 11.250019,-0.53575 C 11.250019,-1.621375 12.133151,-2.5 13.224411,-2.5 z"
id="rect3797" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3799">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6403,-8.5e-3,-7.7e-3,-0.6279,-253.8748,-567.9111)"
r="40.033199"
cy="-970.48578"
cx="432.98141"
id="XMLID_24_">
<stop
id="stop59"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop61"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_24_)"
id="path63"
d="M 17.146,9.856 L 15.924,35.848 L 15.852,37.041 C 17.109,37.752 18.348,38.38 19.663,38.908 C 19.682,38.914 19.716,38.9 19.735,38.908 C 23.662,40.465 27.821,41.179 31.964,41.145 C 32.657,41.14 33.357,41.12 34.05,41.072 C 35.435,40.973 36.85,40.816 38.22,40.55 C 39.591,40.285 40.909,39.932 42.249,39.504 C 44.258,38.861 46.254,38.043 48.146,37.042 L 48.075,35.849 L 46.925,9.857 L 17.146,9.857 L 17.146,9.856 z" />
</g><path
style="fill:url(#linearGradient2727);fill-opacity:1"
d="M 103.22179,40.25 L 139.77334,40.25 C 140.8691,40.25 141.75111,41.128625 141.75111,42.21425 L 141.75111,78.789125 C 141.75111,79.87475 140.8691,80.75 139.77334,80.75 L 103.22179,80.75 C 102.13278,80.75 101.25077,79.873625 101.25077,78.789125 L 101.25077,42.21425 C 101.25077,41.128625 102.13278,40.25 103.22179,40.25 z"
id="rect3929" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3931">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-238.6489,-561.7972)"
r="40.036499"
cy="-1022.5366"
cx="533.49512"
id="XMLID_25_">
<stop
id="stop73"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop75"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_25_)"
id="path77"
d="M 97.145,47.856 L 95.918,73.85 L 95.851,75.04 C 97.113,75.751 98.35,76.383 99.665,76.908 C 99.682,76.914 99.716,76.902 99.731,76.908 C 103.661,78.47 107.824,79.181 111.963,79.148 C 112.654,79.142 113.357,79.125 114.047,79.075 C 115.434,78.979 116.849,78.821 118.221,78.55 C 119.591,78.29 120.912,77.936 122.25,77.506 C 124.262,76.863 126.252,76.045 128.147,75.04 L 128.075,73.85 L 126.926,47.857 L 97.145,47.857 L 97.145,47.856 z" />
</g><path
style="fill:url(#linearGradient2729);fill-opacity:1"
d="M 61.599815,40.25 L 98.148,40.25 C 99.24263,40.25 100.12576,41.128625 100.12576,42.21425 L 100.12576,78.789125 C 100.12576,79.87475 99.24263,80.75 98.148,80.75 L 61.599815,80.75 C 60.511931,80.75 59.625423,79.873625 59.625423,78.789125 L 59.625423,42.21425 C 59.625423,41.128625 60.511931,40.25 61.599815,40.25 z"
id="rect3935" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3937">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6403,-8.5e-3,-7.7e-3,-0.6279,-245.3333,-561.7972)"
r="40.038898"
cy="-1021.9087"
cx="486.17969"
id="XMLID_26_">
<stop
id="stop87"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop89"
style="stop-color:#FFFFFF"
offset="1" />
</radialGradient>
<path
style="fill:url(#XMLID_26_)"
id="path91"
d="M 60.145,47.856 L 58.923,73.85 L 58.852,75.04 C 60.112,75.751 61.347,76.383 62.663,76.908 C 62.684,76.914 62.715,76.902 62.735,76.908 C 66.662,78.47 70.824,79.181 74.964,79.148 C 75.654,79.142 76.357,79.125 77.048,79.075 C 78.441,78.979 79.856,78.821 81.227,78.55 C 82.597,78.29 83.913,77.936 85.25,77.506 C 87.263,76.863 89.258,76.045 91.153,75.04 L 91.076,73.85 L 89.926,47.857 L 60.145,47.857 L 60.145,47.856 z" />
</g><path
style="fill:url(#linearGradient2731)"
d="M 19.971092,40.25 L 56.529397,40.25 C 57.617282,40.25 58.500414,41.128625 58.500414,42.21425 L 58.500414,78.789125 C 58.500414,79.87475 57.617282,80.75 56.529397,80.75 L 19.971092,80.75 C 18.883208,80.75 18.000075,79.873625 18.000075,78.789125 L 18.000075,42.21425 C 18.000075,41.128625 18.883208,40.25 19.971092,40.25 z"
id="rect3941" /><g
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)"
nodetypes="cccsssssscccc"
id="path3943"
style="filter:url(#filter3372)">
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6404,-8.5e-3,-7.7e-3,-0.6279,-252.0475,-561.7972)"
r="40.039001"
cy="-1021.27"
cx="438.85059"
id="XMLID_27_">
<stop
id="stop101"
style="stop-color:#E8E8E8"
offset="0" />
<stop
id="stop103"
style="stop-color:#FFFFFF"
offset="0.5858" />
</radialGradient>
<path
style="fill:url(#XMLID_27_)"
id="path105"
d="M 23.146,47.856 L 21.921,73.85 L 21.849,75.04 C 23.11,75.751 24.348,76.383 25.663,76.908 C 25.682,76.914 25.715,76.902 25.734,76.908 C 29.662,78.47 33.821,79.181 37.965,79.148 C 38.658,79.142 39.358,79.125 40.049,79.075 C 41.436,78.979 42.851,78.821 44.222,78.55 C 45.593,78.29 46.911,77.936 48.252,77.506 C 50.262,76.863 52.257,76.045 54.15,75.04 L 54.078,73.85 L 52.926,47.857 L 23.146,47.857 L 23.146,47.856 z" />
</g><g
style="filter:url(#filter3460)"
id="g109"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 22.213,81.018 L 23.666,76.231 C 23.666,76.231 23.743,74.036 21.849,75.041 L 21.921,76.231 L 20.291,81.018 C 20.605,81.49 21.142,81.8 21.752,81.8 L 23.515,81.8 C 22.971,81.8 22.493,81.489 22.213,81.018 z"
id="path111"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3464)"
id="g113"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 54.079,76.23 L 54.151,75.04 C 52.89,74.329 52.333,76.23 52.333,76.23 L 53.779,81.033 C 53.498,81.496 53.023,81.8 52.485,81.8 L 54.249,81.8 C 54.853,81.8 55.386,81.496 55.701,81.033 L 54.079,76.23 z"
id="path115"
style="fill:#ffffff" />
</g><path
d="M 24.1,48.856 C 23.693,57.45 52.262,55.434 51.971,48.856 C 42.68,48.856 33.39,48.856 24.1,48.856"
id="path122"
style="fill:url(#linearGradient2979);fill-opacity:1;filter:url(#filter3500)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><g
style="filter:url(#filter3468)"
id="g126"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 59.513,81.018 L 60.966,76.231 C 60.966,76.231 61.043,74.036 59.149,75.041 L 59.221,76.231 L 57.591,81.018 C 57.905,81.49 58.442,81.8 59.052,81.8 L 60.815,81.8 C 60.271,81.8 59.792,81.489 59.513,81.018 z"
id="path128"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3472)"
id="g130"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 91.379,76.23 L 91.451,75.04 C 90.189,74.329 89.633,76.23 89.633,76.23 L 91.078,81.033 C 90.797,81.496 90.322,81.8 89.784,81.8 L 91.548,81.8 C 92.152,81.8 92.685,81.496 93,81.033 L 91.379,76.23 z"
id="path132"
style="fill:#ffffff" />
</g><path
d="M 61.399,48.856 C 60.992,57.45 89.56,55.434 89.269,48.856 C 79.98,48.856 70.689,48.856 61.399,48.856"
id="path139"
style="fill:url(#linearGradient2981);filter:url(#filter3496)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><g
id="g143"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 15.921,43.018 L 17.374,38.231 C 17.374,38.231 17.451,36.036 15.557,37.041 L 15.629,38.231 L 14,43.018 C 14.314,43.49 14.851,43.8 15.461,43.8 L 17.224,43.8 C 16.68,43.8 16.201,43.489 15.921,43.018 z"
id="path145"
style="fill:#ffffff;filter:url(#filter3424)" />
</g><path
id="path149"
d="M 47.788,38.23 L 47.86,37.04 C 46.598,36.329 46.042,38.23 46.042,38.23 L 47.487,43.033 C 47.206,43.496 46.731,43.8 46.193,43.8 L 47.957,43.8 C 48.561,43.8 49.094,43.496 49.409,43.033 L 47.788,38.23 z"
style="fill:#ffffff;filter:url(#filter3416)"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)" /><path
d="M 17.808,10.856 C 17.401,19.45 45.969,17.434 45.678,10.856 C 36.389,10.856 27.098,10.856 17.808,10.856"
id="path156"
style="fill:url(#linearGradient3380);fill-opacity:1;filter:url(#filter3504)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3444)"
id="g160"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 53.513,43.018 L 54.966,38.231 C 54.966,38.231 55.043,36.036 53.149,37.041 L 53.221,38.231 L 51.591,43.018 C 51.905,43.49 52.442,43.8 53.052,43.8 L 54.815,43.8 C 54.271,43.8 53.792,43.489 53.513,43.018 z"
id="path162"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3448)"
id="g164"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 85.379,38.23 L 85.451,37.04 C 84.189,36.329 83.633,38.23 83.633,38.23 L 85.078,43.033 C 84.797,43.496 84.322,43.8 83.784,43.8 L 85.548,43.8 C 86.152,43.8 86.685,43.496 87,43.033 L 85.379,38.23 z"
id="path166"
style="fill:#ffffff" />
</g><path
d="M 55.399,10.856 C 54.992,19.45 83.56,17.434 83.269,10.856 C 73.98,10.856 64.689,10.856 55.399,10.856"
id="path173"
style="fill:url(#linearGradient2945);fill-opacity:1;filter:url(#filter3508)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3452)"
id="g177"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 90.513,43.018 L 91.966,38.231 C 91.966,38.231 92.043,36.036 90.149,37.041 L 90.221,38.231 L 88.592,43.018 C 88.905,43.49 89.442,43.8 90.053,43.8 L 91.815,43.8 C 91.271,43.8 90.793,43.489 90.513,43.018 z"
id="path179"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3456)"
id="g181"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 122.379,38.23 L 122.451,37.04 C 121.189,36.329 120.633,38.23 120.633,38.23 L 122.078,43.033 C 121.797,43.496 121.322,43.8 120.784,43.8 L 122.548,43.8 C 123.152,43.8 123.685,43.496 124,43.033 L 122.379,38.23 z"
id="path183"
style="fill:#ffffff" />
</g><path
d="M 92.399,10.856 C 91.992,19.45 120.56,17.434 120.269,10.856 C 110.98,10.856 101.689,10.856 92.399,10.856"
id="path190"
style="fill:url(#linearGradient3378);fill-opacity:1;filter:url(#filter3512)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-17.235103)" /><g
style="filter:url(#filter3476)"
id="g194"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 96.513,81.018 L 97.966,76.231 C 97.966,76.231 98.043,74.036 96.149,75.041 L 96.221,76.231 L 94.592,81.018 C 94.905,81.49 95.442,81.8 96.053,81.8 L 97.815,81.8 C 97.271,81.8 96.793,81.489 96.513,81.018 z"
id="path196"
style="fill:#ffffff" />
</g><g
style="filter:url(#filter3484)"
id="g198"
transform="matrix(1.1250094,0,0,1.125,-4.5001124,-11.275)">
<path
d="M 128.379,76.23 L 128.451,75.04 C 127.189,74.329 126.633,76.23 126.633,76.23 L 128.078,81.033 C 127.797,81.496 127.322,81.8 126.784,81.8 L 128.548,81.8 C 129.152,81.8 129.685,81.496 130,81.033 L 128.379,76.23 z"
id="path200"
style="fill:#ffffff" />
</g><path
d="M 98.399,48.856 C 97.992,57.45 126.56,55.434 126.269,48.856 C 116.98,48.856 107.689,48.856 98.399,48.856"
id="path207"
style="fill:url(#linearGradient2983);filter:url(#filter3492)"
transform="matrix(1.1250094,0,0,1.7718407,-4.5001124,-41.815049)" /><path
style="fill:#323232"
id="path209"
d="M 21.99503,12.125 L 19.778139,23.842759 C 19.61869,24.635599 19.540614,25.268992 19.540614,25.848502 C 19.540614,27.088895 20.172912,27.749779 21.150501,27.749779 C 22.470079,27.749779 23.261827,26.799689 23.788558,23.974716 L 26.058233,12.125 L 28.723782,12.125 L 26.533281,23.632728 C 25.715142,27.960908 24.079966,30.125 20.832702,30.125 C 18.27382,30.125 16.875066,28.645985 16.875066,26.112415 C 16.875066,25.399849 16.981732,24.502542 17.166473,23.47328 L 19.330582,12.125 L 21.99503,12.125 L 21.99503,12.125 z" /><path
style="fill:#323232"
id="path211"
d="M 64.643632,12.125 L 61.198712,30.125 L 58.500414,30.125 L 61.918629,12.125 L 64.643632,12.125 L 64.643632,12.125 z" /><path
style="fill:#323232"
id="path213"
d="M 111.34148,17.759442 C 111.34148,21.280968 110.11546,26.238278 107.82102,28.532705 C 106.7515,29.602206 105.57766,30.125 104.19402,30.125 C 100.56813,30.125 100.12576,26.368704 100.12576,24.464472 C 100.12576,20.995117 101.35178,16.037807 103.77666,13.664038 C 104.79508,12.672793 105.99394,12.125 107.35038,12.125 C 110.95129,12.125 111.34148,15.62044 111.34148,17.759442 z M 105.60374,15.124819 C 103.82883,16.899712 102.83867,21.985274 102.83867,24.593812 C 102.83867,25.793739 102.94301,27.802314 104.58532,27.802314 C 105.05486,27.802314 105.4983,27.567546 105.91676,27.15018 C 107.76775,25.270946 108.60358,19.585419 108.60358,17.629015 C 108.60358,16.089978 108.47315,14.446599 106.98627,14.446599 C 106.48956,14.447686 106.0211,14.681367 105.60374,15.124819 z" /><path
style="fill:#323232"
id="path215"
d="M 30.011882,54.875 L 32.678531,54.875 L 30.223015,67.727587 C 29.431267,71.819347 27.794991,72.875 25.182225,72.875 C 24.522435,72.875 23.91543,72.768335 23.625122,72.663868 L 24.073779,70.288648 C 24.443261,70.394213 24.785251,70.420605 25.287791,70.420605 C 26.450121,70.420605 27.161593,69.681647 27.531075,67.728687 L 30.011882,54.875 z" /><path
style="fill:#323232"
id="path217"
d="M 68.668474,54.876125 L 71.338789,54.876125 L 69.683194,63.422176 L 69.763303,63.422176 C 70.350773,62.407464 70.804726,61.659781 71.285383,60.885397 L 75.156227,54.877238 L 78.146981,54.877238 L 72.512615,62.72901 L 74.782383,72.876125 L 71.978553,72.876125 L 70.428657,64.732844 L 69.148018,66.387312 L 67.895195,72.875012 L 65.25047,72.875012 L 68.668474,54.876125 z" /><path
style="fill:#323232"
id="path219"
d="M 110.29425,54.873875 L 113.0183,54.873875 L 110.02717,70.523727 L 114.67522,70.523727 L 114.22121,72.873875 L 106.87582,72.873875 L 110.29425,54.873875 z" /></g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

98
imgsrc/languages.svg Normal file
View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="128" width="128" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256">
<defs>
<linearGradient id="b" y2="158.07" gradientUnits="userSpaceOnUse" x2="141.27" gradientTransform="matrix(1.68, 0, 0, 1.68, -86.7, -86.7)" y1="70.428" x1="141.27">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#00a200" offset="1"/>
</linearGradient>
<linearGradient id="a" y2="158.07" gradientUnits="userSpaceOnUse" y1="70.428" gradientTransform="matrix(1.68, 0, 0, 1.68, -86.7, -86.7)" x2="141.27" x1="141.27">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#00a100" offset="0.5"/>
<stop stop-color="#000" offset="1"/>
</linearGradient>
<linearGradient id="c" y2="397.34" gradientUnits="userSpaceOnUse" x2="12.991" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" y1="397.34" x1="-117">
<stop stop-color="#0053BD" offset="0"/>
<stop stop-color="#0032A4" offset="1"/>
</linearGradient>
<radialGradient id="d" gradientUnits="userSpaceOnUse" cy="439.63" cx="-57.022" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" r="98">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#57ADFF" offset="0.6"/>
<stop stop-color="#C9E6FF" offset="1"/>
</radialGradient>
<linearGradient id="e" y2="183.37" gradientUnits="userSpaceOnUse" x2="127.66" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="63.215" x1="127.66">
<stop stop-color="#006a00" offset="0"/>
<stop stop-color="#004000" offset="0.2"/>
<stop stop-color="#00d000" offset="1"/>
</linearGradient>
<linearGradient id="f" y2="361.42" gradientUnits="userSpaceOnUse" x2="-52.251" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" y1="457.03" x1="-52.251">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#94CAFF" offset="1"/>
</linearGradient>
<linearGradient id="g" y2="158.07" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="141.27" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="70.428" x1="141.27"/>
<linearGradient id="h" y2="130.03" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="100.51" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="70.033" x1="100.51"/>
<linearGradient id="i" y2="85.32" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="120.48" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="68.117" x1="120.48"/>
<linearGradient id="j" y2="79.161" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="124.57" y1="73.444" x1="124.57"/>
<linearGradient id="k" y2="73.865" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="132.78" y1="67.756" x1="132.78"/>
<linearGradient id="l" y2="323.36" gradientUnits="userSpaceOnUse" x2="258.77" gradientTransform="translate(5.58, -12.8322)" y1="408.7" x1="258.77">
<stop stop-color="#3434ff" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="m" y2="85.792" gradientUnits="userSpaceOnUse" x2="-60.735" gradientTransform="translate(2.16, -1.33)" y1="171.13" x1="-60.735">
<stop stop-color="#ffff01" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="n" y2="298.71" gradientUnits="userSpaceOnUse" x2="-105.42" y1="384.04" x1="-105.42">
<stop stop-color="red" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="o" y2="408.7" gradientUnits="userSpaceOnUse" x2="32.595" gradientTransform="translate(-3.45, -0.43)" y1="494.61" x1="32.595">
<stop stop-color="lime" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="p" y2="99.849" gradientUnits="userSpaceOnUse" x2="230.67" gradientTransform="translate(1.59, 1.61)" y1="171.13" x1="230.67">
<stop stop-color="#F0F" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
</defs>
<g transform="translate(-3.417, 1.068)">
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M-39.634,171.47c-31.743,31.66-49.227,73.53-49.227,117.89,0,92.35,75.02,167.48,167.23,167.48,92.218,0,167.25-75.13,167.25-167.48,0-92.06-75.03-166.96-167.25-166.96-44.38,0.01-86.288,17.43-118.01,49.07z" fill="#000"/>
<path d="M-43.9,167.2c-31.744,31.66-49.228,73.53-49.228,117.89,0,92.35,75.02,167.48,167.23,167.48,92.225,0,167.24-75.13,167.24-167.48,0-92.06-75.02-166.96-167.24-166.96-44.38,0.01-86.287,17.43-118,49.07z" fill="url(#c)"/>
<path d="M-39.03,172.09c-30.439,30.35-47.207,70.49-47.207,113,0,88.55,71.929,160.59,160.34,160.59,88.42,0,160.35-72.04,160.35-160.59,0-88.25-71.93-160.06-160.35-160.06-42.533,0.01-82.714,16.72-113.13,47.06z" fill="#b0d9ff"/>
<path d="M74.105,440.51c85.675,0,155.18-69.8,155.18-155.42,0-85.08-69.51-154.88-155.18-154.88-85.068,0-155.16,69.8-155.16,154.88,0,85.62,70.098,155.42,155.16,155.42z" fill="#FFF"/>
<path d="M74.105,440.51c85.675,0,155.18-69.8,155.18-155.42,0-85.08-69.51-154.88-155.18-154.88-85.068,0-155.16,69.8-155.16,154.88,0,85.62,70.098,155.42,155.16,155.42z" fill="url(#d)"/>
<path d="M22.564,147.28c-0.767,0-1.608,0.31-2.467,0.8,2.019-0.59,2.969-0.8,2.467-0.8m49.961,9.74l0.502-7.31-7.712,0.49,1.019,6.82h6.191m-87.044,126.31c-1.554-1.46-0.512-7.81-0.512-7.81s-23.183-12.18-48.417-19.5c-3.111-0.9-1.541-7.31,1.539-9.74l-1.022-6.84c-0.512-3.41,5.157-19.99,10.82-21.44,5.669-1.47-0.517,9.74-0.517,9.74l-5.661,3.41s6.693,7.8,8.244,7.8c1.542,0,4.117-3.9,4.117-3.9l-7.207-4.87,6.695-2.93,0.422-2.57,1.125-0.36,11.087-16.79c7.662-3.14,17.08-7.02,18.276-7.59,2.064-0.97,16.478-9.26,19.056-11.2,2.5833-1.97,8.2368-1.47,10.292-1.47,2.072,0,5.157-0.98,5.669-6.35,0.514-5.36,2.578-6.34,4.127-4.87,1.549,1.45-1.549,3.9,2.061,4.87,3.602,0.98,6.69,3.42,9.273,0.98,1.881-1.78-0.638-3.82-2.336-5.35h28.599l3.096-8.8-6.695-0.97-24.735-2.43v-2.93l-1.971,0.33c2.679-14.71,18.58-12.29,6.608-20.8-0.729-0.53-11.203,16.5-13.869,16.17-4.83-0.64-11.067-0.69-12.395,0.88-1.7595,2.08,3.95-7.13,8.862-9.92-7.845,2.31-32.626,10.79-58.82,38.7-25.046,26.67-35.032,62.62-35.032,63.88,0,2.43,5.156,3.41,5.668,6.34,0.518,2.91-9.785,12.68-9.785,17.55,0,2.25-2.426,32.3,6.17,57.06,10.051,28.89,31.505,53.03,34.001,54.62l5.146-2.44s-11.847-20.97-12.359-22.93c-0.509-1.94,13.391-30.23,20.093-29.24,6.693,0.95,5.152,2.92,9.271,0.48,4.122-2.43,6.6951-22.44,11.332-24.38,4.6392-1.96,9.7853-4.39,9.2733-9.27-0.5378-4.89-18.047-13.66-19.589-15.12m111.77-143.37l-13.905-4.88,2.578,7.81,11.327-2.93m-52.022,18.04c1.552,0,32.449-20.47,29.359-20.96-3.08-0.49-3.598,0-11.854-0.98-8.229-0.98-16.993,11.7-19.049,13.66-2.061,1.95-1.366,8.28,1.544,8.28m141.8,147.04l3.99-5.18-3.99-1.4-2.99,3.76-3.48,5.16,2.99,1.41,3.48-3.75m17.45,16.93l-1-7.53h-6.48l-0.5,5.64-5.99-0.93-1.48-6.12-2.99-1.88-3.5,4.23-3.48-0.94-1,3.29,3.99,0.95v30.54l14.11,3.4c-0.33,0.56-0.57,1.03-0.65,1.29-1.01,3.29,3.98,4.72,7.46,3.29,1.3-0.51,5.4-4.66,7.98-10.8,3.59-8.54,6.73-19.82,7.36-23.8l1.61-3.45-9.97,3.75-5.49-0.93h0.02m20.94-56.88l-2.61-5.04c-3.08-17.39-10.15-39.64-25.3-62.64-22.8-34.57-86.26-54.51-86.26-54.51l-3.48,4.22-2-3.28-4.99-1.89v4.24l4.5,3.76-2.99,1.41-11.476,0.93-25.918,14.11,2.488,11.28-2.997,0.94-1.483,2.35,8.474,12.68,0.496,4.24-6.978,1.41v8.45l-3.986,0.94,0.5,6.58-33.906,23.52,0.999,13.14c2.488,3.29,21.935,23.04,21.935,23.04s22.429,0.92,27.413-1.88c4.987-2.82,1.493,2.82,2.995,4.23,1.488,1.42,1.989,11.28,3.482,12.22,1.495,0.93,0,6.57,1.994,8.46,1.991,1.87,1.991,24.44,1.991,24.44s11.967,20.2,11.967,25.37c0,5.18-0.504,4.7,8.97,4.24,9.48-0.47,11.47-4.24,13.45-5.64,2.01-1.41,2.01-4.7,4-7.52,2-2.83,5.48-13.63,9.98-17.39,4.48-3.77,16.45-6.59,17.43-13.16,1-6.58,5.49-11.75,5.49-11.75l21.55-22.8-0.6,3.06-0.5,11.74,6.48-2.34-0.49-12.7-2.33-2.46,0.33-0.36s-1.49-2.82-3.48-2.82-13.97,2.82-15.95,2.35c-2-0.47-10.48-23.03-11.97-23.96-1.5-0.94-10.97-16.46-10.97-16.46s21.93,26.32,25.43,36.66c2.02,6.02,9.63,0.41,15.82-5.87l1.64,4,3.98-0.95-0.5-4.7h4.48v7.05l-1.49,3.76-0.51,6.1,3.99,3.77,2-3.28,6.47-6.12,7.48-3.76,2,3.76,1,5.18-2,5.63-3.99,3.29-1.99,8.46v4.23l-4.48-2.82-0.49-8.93-6.49,0.48-2.99,7.98,4.49,6.59,10.46,1.41,8.48-8,1-15.49,3.77-4.98c2.45,6.31,4.21,12.92,4.21,19.08,0,6.74,3.08-4.68,0.46-25.26l1.52-2.02m-125.63-47.94l-26.917-0.95,11.46-9.39h5.983l9.474,6.57v3.77m32.913-3.29v4.23h-11.46l0.99,2.83-6.98,0.95-0.49,2.34-4.99-0.93-8.98-1.89,1.5-2.34,1.5-2.84,4.98-5.16,2,3.76,7.48-0.48,3.98-4.23,15.46,2.82-4.99,0.94m0.98-6.11l-5.97,0.94-1-4.24,7.47-0.93,1-4.23,5.5,5.65-7,2.8v0.01m28.93,146.16l-3.5,2.82,0.5,7.06h4.49v-6.1l3.99-5.18v-10.81l-2.5-0.48-2.98,12.69m-33.4-14.08s-3.49,0.91,0.49,2.33,19.94-23.01,19.94-23.01l-13.46,8.45-6.98,12.23h0.01m-27.14,90.07l-2.978-2.84-5.985-0.95-0.991,2.84-7.976-0.94-0.499-3.78h-5.98l-6.476,3.78h-11.458l-0.996-2.84-18.433-1.9-2.995,2.84-7.462-1.88-1.001-6.63-3.487-0.49-3.988,7.12-13.452-0.47c2.4088,1.13,22.491,13.12,53.301,15.61,40.856,3.31,60.296-6.62,60.296-6.62l-1.5-1.43-17.94-1.41v-0.01z" fill="url(#e)"/>
<path d="M73.583,254c53.147,0,99.387-18.31,123.18-45.31-23.96-45.72-70.24-76.92-123.41-76.92-52.699,0-99.045,31.1-123.15,76.69,23.726,27.12,70.075,45.54,123.38,45.54z" fill="url(#f)"/>
<path d="M141.67,229.84s3.61,4.33,8.13,10.17c19.53-7.86,35.75-18.61,46.96-31.32-7.94-15.15-18.35-28.7-30.64-40.06-25.84-16.6-54.87-25.73-54.87-25.73l-3.48,4.22-2-3.28-4.99-1.89v4.24l4.5,3.76-2.99,1.41-11.476,0.93-25.918,14.11,2.488,11.28-2.997,0.94-1.483,2.35,8.474,12.68,0.496,4.24-6.978,1.41v8.45l-3.986,0.94,0.5,6.58-33.906,23.52,0.803,10.55c14.209,3,29.423,4.66,45.276,4.66,27.537,0,53.187-4.94,74.847-13.44-3.12-4.76-6.76-10.72-6.76-10.72zm-41.883-12.69l-26.917-0.95,11.46-9.39h5.983l9.474,6.57v3.77zm32.913-3.29v4.23h-11.46l0.99,2.83-6.98,0.95-0.49,2.34-4.99-0.93-8.98-1.89,1.5-2.34,1.5-2.84,4.98-5.16,2,3.76,7.48-0.48,3.98-4.23,15.46,2.82-4.99,0.94zm0.98-6.12v0.01l-5.97,0.94-1-4.24,7.47-0.93,1-4.23,5.5,5.65-7,2.8z" fill="url(#g)"/>
<path d="M-15.54,199.94c2.063-0.97,16.478-9.26,19.056-11.2,2.5833-1.97,8.2361-1.47,10.292-1.47,2.071,0,5.156-0.98,5.669-6.35,0.514-5.36,2.578-6.34,4.127-4.87,1.549,1.45-1.549,3.9,2.061,4.87,3.602,0.98,6.69,3.42,9.273,0.98,1.881-1.78-0.638-3.82-2.336-5.35h28.599l3.095-8.8-6.695-0.97-24.734-2.43v-2.93l-1.971,0.33c2.678-14.71,18.58-12.29,6.607-20.8-0.728-0.53-11.203,16.5-13.868,16.17-4.83-0.64-11.067-0.69-12.395,0.88-1.7598,2.08,3.95-7.13,8.862-9.92-6.106,1.8-22.478,7.38-41.781,22.9-11.17,10.8-20.701,23.45-28.121,37.48,2.789,3.19,5.918,6.25,9.309,9.18l6.674-10.11c7.663-3.13,17.083-7.02,18.277-7.59z" fill="url(#h)"/>
<path d="M43.679,149.72c-2.059,1.95-1.364,8.28,1.546,8.28,1.552,0,32.449-20.47,29.359-20.96-3.08-0.49-3.598,0-11.854-0.98-8.232-0.98-16.996,11.7-19.051,13.66z" fill="url(#i)"/>
<polygon points="126.07,73.444,123.08,73.631,123.47,76.284,125.88,76.284,125.88,76.285,126.07,73.444" transform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" fill="url(#j)"/>
<polygon points="135.49,69.653,130.08,67.756,131.08,70.792,135.49,69.653" transform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" fill="url(#k)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path d="M247.4,375.48l15.77,2.64c-2.02,5.79-5.23,10.19-9.6,13.21-4.38,3.02-9.85,4.54-16.42,4.54-10.4,0-18.1-3.4-23.09-10.2-3.95-5.44-5.92-12.32-5.92-20.61,0-9.92,2.59-17.68,7.78-23.3,5.18-5.61,11.73-8.42,19.65-8.42,8.9,0,15.92,2.94,21.07,8.82,5.14,5.88,7.6,14.88,7.38,27.01h-39.66c0.12,4.69,1.39,8.34,3.83,10.95,2.45,2.61,5.49,3.92,9.13,3.92,2.48,0,4.56-0.68,6.25-2.03s2.97-3.53,3.83-6.53zm0.9-16c-0.11-4.58-1.29-8.07-3.55-10.45-2.25-2.38-4.99-3.58-8.22-3.58-3.45,0-6.31,1.26-8.56,3.78-2.25,2.51-3.36,5.93-3.32,10.25h23.65zm-20.22-32.11l7.78-16.84h17.74l-15.49,16.84h-10.03z" stroke="#000064" stroke-width="10" fill="none"/>
<path fill-opacity="0.3" d="M236.03,308.41l-1.72,3.71-7.78,16.85-3.06,6.62c-2.91,1.64-5.67,3.57-8,6.1-6.46,6.99-9.47,16.59-9.47,27.62,0,9.31,2.28,17.68,7.12,24.38l0.04,0.03c6.25,8.5,16.45,12.81,28.25,12.81,7.55,0,14.45-1.78,20.06-5.65,5.56-3.85,9.61-9.57,12-16.38l2.53-7.16-1.38-0.25,0.07-3.53c0.24-13.05-2.36-23.79-8.97-31.34-4.14-4.73-9.57-7.82-15.66-9.5l12.5-13.6,9.88-10.71h-36.41zm-2.75,36.59c-2.2,1.01-4.2,2.42-5.81,4.22-3.48,3.88-5.02,9.15-4.97,14.59l0.03,3.22h-0.47l0.16,6.56c0.14,5.78,1.85,11.19,5.56,15.16,1.5,1.6,3.33,2.86,5.28,3.84-4.13-1.16-7.22-3.21-9.59-6.43-3.04-4.2-4.66-9.56-4.66-16.85,0-8.8,2.16-14.7,6.07-18.93,2.5-2.72,5.24-4.41,8.4-5.38zm15.94,0.59c0.6,0.25,1.1,0.64,1.66,0.94-0.53-0.36-1.1-0.63-1.66-0.94zm1.72,0.97c1.9,1.05,3.63,2.35,5.15,4.1,2.93,3.34,4.95,8.71,5.57,16.37h-2.6l-0.09-3.44c-0.14-5.6-1.66-10.85-5.28-14.68-0.83-0.88-1.77-1.66-2.75-2.35zm-10.13,9.56c1.43,0.01,2.18,0.34,3.13,1.22h-6.44c0.91-0.86,1.7-1.21,3.31-1.22zm-3.78,23.72h7.75c-0.38,0.77-0.75,1.3-0.94,1.44-0.49,0.4-0.93,0.63-2.25,0.63-2.11,0-3.11-0.43-4.47-1.88-0.04-0.04-0.05-0.14-0.09-0.19zm18.53,7.07l1.97,0.31c-1,1.18-2.09,2.26-3.34,3.12-1.14,0.79-2.44,1.38-3.85,1.91,0.51-0.31,1.04-0.6,1.5-0.97,1.54-1.23,2.75-2.72,3.72-4.37z" fill="#000"/>
<path d="M247.4,375.48l15.77,2.64c-2.02,5.79-5.23,10.19-9.6,13.21-4.38,3.02-9.85,4.54-16.42,4.54-10.4,0-18.1-3.4-23.09-10.2-3.95-5.44-5.92-12.32-5.92-20.61,0-9.92,2.59-17.68,7.78-23.3,5.18-5.61,11.73-8.42,19.65-8.42,8.9,0,15.92,2.94,21.07,8.82,5.14,5.88,7.6,14.88,7.38,27.01h-39.66c0.12,4.69,1.39,8.34,3.83,10.95,2.45,2.61,5.49,3.92,9.13,3.92,2.48,0,4.56-0.68,6.25-2.03s2.97-3.53,3.83-6.53zm0.9-16c-0.11-4.58-1.29-8.07-3.55-10.45-2.25-2.38-4.99-3.58-8.22-3.58-3.45,0-6.31,1.26-8.56,3.78-2.25,2.51-3.36,5.93-3.32,10.25h23.65zm-20.22-32.11l7.78-16.84h17.74l-15.49,16.84h-10.03z" fill="url(#l)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M25.844,80.688c-3.427,0.201-6.099,1.831-7.969,3.968-0.97,1.099-1.8,2.437-2.344,4-2.276,0.558-4.235,1.611-5.656,3.032l-0.0312-0.032c-0.1083,0.101-0.2094,0.206-0.3126,0.313-0.0412,0.042-0.0845,0.081-0.125,0.125l-0.1562,0.156-0.125,0.156c-1.8144,2.203-3.1928,5.222-2.5625,9.034,0.4099,2.47,1.6306,4.23,2.9063,5.62-0.4704-0.02-0.9399-0.09-1.4063-0.09h-14.75c-0.0153-1.66-0.0326-3.43-0.0937-6.13v-2.871c0.0081-0.22,0.0081-0.218,0-0.438v-0.219c-0.2142-2.996-1.4385-6.385-4.5628-8.781-2.742-2.103-5.854-2.492-8.312-2.406v-0.063h-0.313c-2.23,0.081-5.351,0.632-8,2.782-3.122,2.534-4.187,5.874-4.187,9.125-0.004,0.146-0.004,0.135,0,0.281v0.188c0.148,2.732,0.27,5.532,0.344,8.342,0.001,0.07-0.002,0.12,0,0.19h-17.563c-2.952,0-6.189,0.93-8.719,3.78-2.285,2.57-2.885,5.66-2.968,7.91v0.03c-0.077,2.15,0.238,5.34,2.343,8.12,2.478,3.27,5.856,4.32,9,4.31h15.875c-1.561,6.33-4.261,11.82-8.281,16.69-3.494,4.25-8.049,8.02-13.844,11.28-2.61,1.47-5.285,3.91-6.437,7.66-0.904,2.94-0.651,5.87,0.594,8.59,0.075,0.17,0.135,0.34,0.218,0.5,0.133,0.32,0.25,0.63,0.25,0.63l0.063,0.09c0.007,0.01,0.024,0.02,0.031,0.03l0.031,0.07c-0.541-0.91-0.521-0.79,0.157,0.28l3.093,4.87,1.563-0.78c0.986,0.54,1.933,1.15,3.062,1.41,3.446,0.79,6.763,0.03,9.406-1.38l0.219-0.12,0.125-0.1c0.021-0.01,0.042-0.02,0.063-0.03,0.111-0.07,0.235-0.14,0.343-0.22v-0.03c10.4-6.22,18.697-14.33,24.75-24.03h0.376l1.812-3.34c3.595-6.66,6.063-14.07,7.6875-22.07h9.5625c-0.44,5.74-0.9,11.66-1.1562,14.1v0.09l-0.03,0.22c-0.5064,5.19-1.1158,9.15-1.5625,10.94-0.2162,0.78-0.4454,1.14-0.625,1.47h-6.4688c-2.9742,0-6.5122,0.82-9.3122,3.62-2.488,2.49-3.5,5.52-3.5,8.63-0.011,0.25-0.011,0.24,0,0.5v0.25c0.061,0.73,0.386,1.33,0.562,2l-1.344,1.34,4.532,4.53c1.718,1.72,1.831,1.74,0.187,0.16-0.202-0.2-0.188-0.18,0.063,0.06,2.71,2.56,6.053,3.5,9.156,3.5l7.2812,0.13h0.1876c6.6615,0,12.942-2.73,17.25-7.57h0.719l1.812-3.15,0.031-0.03c2.821-4.91,4.094-11.02,5.313-19.07l0.156-0.31,0.125-1.06c0.479-4.07,0.943-9.08,1.406-15.09,0.467-5.21,0.696-8.98,0.781-11.72,0.104-1.87-0.192-3.7-0.687-5.5,2.84,0.2,5.131-0.73,7.031-2l0.032,0.06c0.073-0.04,0.146-0.06,0.218-0.1l0.688-0.37,0.625-0.53c1.368-1.22,2.599-2.87,3.343-4.94,1.367-0.35,2.474-0.98,3.532-1.69l0.031,0.07c0.073-0.04,0.146-0.06,0.219-0.1l0.687-0.37,0.625-0.53c2.042-1.82,3.909-4.57,4.032-8.32,0.116-3.585-1.544-6.502-3.188-8.34l-0.062-0.062-0.094-0.094c-0.072-0.075-0.144-0.147-0.219-0.219-1.61-1.691-2.862-2.91-4.094-4.063l-0.093-0.093-0.094-0.094c-1.897-1.724-3.728-3.203-5.625-4.469-1.827-1.279-4.511-2.402-7.625-2.218zm-17.75,37.752c0.1539,0.02,0.9063,0.12,0.9062,0.12-0.0002,0,0.3432,0.11,0.625,0.19-0.3653-0.07-1.4337-0.29-1.5312-0.31zm-4.2188,1.34h0.1875c-0.3096,0.23-0.3931,0.27-0.625,0.44,0.0494-0.07,0.25-0.35,0.25-0.34,0,0,0.1622-0.09,0.1875-0.1zm8.063,0.78c0.02,0.01,0.042,0.02,0.062,0.03l-0.625,0.53,0.563-0.56zm0.843,0.53c0.027,0.03,0.037,0.07,0.063,0.1l-0.938,0.65,0.875-0.75zm0.5,0.69c0.093,0.16,0.184,0.31,0.25,0.5-0.048-0.08-0.113-0.26-0.25-0.5zm-11.594,1.03c-0.0233,0.11-0.0516,0.24-0.0937,0.44,0.0065-0.07,0.0312-0.34,0.0312-0.34s0.0587-0.09,0.0625-0.1zm11.906,4.28c-0.003,0.06-0.028,0.16-0.032,0.22-0.137,0.23-1,1.72-1,1.72,0.001,0-0.369,0.28-0.593,0.44,0.331-0.49,1.323-1.94,1.625-2.38zm-10.938,1.6c0.1258,0.16,0.316,0.33,0.4688,0.5l-0.0625,0.06c-0.0913-0.1-0.2038-0.23-0.25-0.28-0.1353-0.16-0.1402-0.2-0.1563-0.22-0.004,0-0.0291-0.03-0.0312-0.03l0.0312-0.03zm0.6876,0.75c0.1283,0.12,0.1857,0.25,0.3437,0.37-0.1548-0.12-0.3158-0.25-0.4063-0.34l0.0626-0.03zm0.5312,0.53c0.2412,0.19,0.5718,0.42,1.25,0.72-0.1829-0.08-0.3407-0.14-0.5938-0.28-0.0098-0.01-0.0212-0.03-0.0312-0.03,0.0002,0-0.4694-0.29-0.625-0.41zm7.031,0.25c-0.319,0.23-0.75,0.53-0.75,0.53s-0.3648,0.06-0.531,0.09c0.399-0.19,0.73-0.35,1.281-0.62z" fill="#000"/>
<path d="M21.968,82.795c-1.392,0.082-2.666,0.824-3.531,1.812-0.889,1.008-1.555,2.502-1.312,4,0.242,1.499,1.174,2.603,2.218,3.438,1.879,1.503,3.31,2.692,4.219,3.531,1.214,1.143,2.159,2.174,2.906,3.125,0.021,0.032,0.041,0.063,0.063,0.094,0.933,1.088,2.154,1.985,3.687,2.185,1.534,0.21,3.014-0.43,4.094-1.341,0.021-0.01,0.042-0.021,0.063-0.032,1.018-0.905,1.857-2.235,1.906-3.75,0.049-1.514-0.646-2.818-1.563-3.843-0.02-0.021-0.041-0.042-0.062-0.063-1.559-1.636-2.918-2.965-4.094-4.062-0.01-0.011-0.021-0.021-0.031-0.032-1.741-1.582-3.356-2.893-4.875-3.906-1.063-0.744-2.266-1.24-3.688-1.156zm-45.968,5.406c-1.526,0.055-2.976,0.36-4.188,1.344-1.228,0.997-1.844,2.662-1.844,4.156-0.001,0.042-0.001,0.084,0,0.125,0.151,2.794,0.269,5.661,0.344,8.534,0.055,2.48,0.026,4.61,0,6.75h-23.969c-1.377,0-2.914,0.51-3.906,1.62-0.992,1.12-1.323,2.52-1.375,3.91-0.05,1.4,0.145,2.82,1.063,4.03,0.917,1.21,2.503,1.78,3.875,1.78h23.5c-1.118,10.52-4.697,19.55-10.969,27.16-4.045,4.91-9.235,9.18-15.625,12.78-1.585,0.89-2.932,2.22-3.469,3.97-0.472,1.53-0.261,3.32,0.563,4.72,0.003,0-0.004,0.02,0,0.03,0.023,0.04,0.037,0.08,0.062,0.12l0.063-0.03c0.814,1.38,2.219,2.37,3.718,2.72,1.664,0.38,3.407,0.04,4.938-0.78,0.032-0.02,0.063-0.04,0.094-0.06,10.928-6.45,19.303-14.87,24.937-25.19h0.031c3.976-7.36,6.576-15.91,8-25.44h20.688c0.9846,0,0.952,0.2,0.875,0.09,0.0205,0.03,0.0413,0.05,0.0625,0.07-0.1075-0.15,0.1515,0.24,0.0937,1.28-0.6844,9.66-1.2515,16.24-1.625,19.75v0.03c-0.5275,5.5-1.1278,9.6-1.75,12.09-0.76,2.76-1.7441,4.35-2.5937,5.07-0.021,0.01-0.0418,0.02-0.0625,0.03-0.8088,0.72-2.0336,1.22-4.125,1.22h-6.5309c-1.748,0-3.534,0.5-4.782,1.75-1.062,1.06-1.625,2.63-1.625,4.09-0.003,0.07-0.003,0.15,0,0.22,0.123,1.47,0.862,2.86,1.907,3.84l-0.032,0.03c0.027,0.03,0.067,0.04,0.094,0.07,0.011,0.01,0.021,0.02,0.031,0.03,1.262,1.19,3.032,1.75,4.75,1.75l7.4066,0.12h0.0313c6.2648,0,11.418-2.61,14.281-7.53h0.0313c2.1206-3.69,3.5126-9.5,4.7496-17.94,0.011-0.02,0.022-0.04,0.032-0.06,0.462-3.93,0.916-8.88,1.375-14.84,0.459-5.13,0.702-8.88,0.781-11.41,0.184-3.32-0.967-6.4-3.406-8.44v-0.03c-2.262-1.85-5.2882-2.62-8.7191-2.62h-21.188c0.036-1.59,0.094-3.12,0.094-4.88,0-1.65-0.049-4.14-0.125-7.498v-3.031c0.002-0.062,0.002-0.125,0-0.187-0.11-1.546-0.755-3.154-2.062-4.157-1.29-0.989-2.871-1.245-4.438-1.156h-0.062zm37.406,2.094c-1.368,0.121-2.581,0.835-3.4689,1.781-0.0431,0.04-0.0848,0.082-0.125,0.125-0.8348,1.014-1.4285,2.481-1.1875,3.938,0.2411,1.457,1.1663,2.537,2.1874,3.343,1.792,1.498,3.121,2.698,4.156,3.658,0.011,0.01,0.021,0.02,0.032,0.03,1.298,1.15,2.297,2.17,3,3.06,0.02,0.03,0.041,0.07,0.062,0.1,0.934,1.09,2.159,2,3.688,2.21,1.529,0.22,3.029-0.42,4.125-1.34,0.021-0.01,0.041-0.02,0.062-0.03,1.026-0.91,1.875-2.27,1.906-3.78,0.032-1.52-0.683-2.8-1.593-3.814-0.021-0.021-0.042-0.042-0.063-0.062-1.547-1.625-2.9-2.98-4.094-4.094-0.01-0.011-0.02-0.021-0.031-0.031-1.727-1.57-3.347-2.912-4.937-4-1.059-0.725-2.349-1.215-3.719-1.094z" stroke="#3c3c00" stroke-width="10" fill="none"/>
<path d="M21.968,82.795c-1.392,0.082-2.666,0.824-3.531,1.812-0.889,1.008-1.555,2.502-1.312,4,0.242,1.499,1.174,2.603,2.218,3.438,1.879,1.503,3.31,2.692,4.219,3.531,1.214,1.143,2.159,2.174,2.906,3.125,0.021,0.032,0.041,0.063,0.063,0.094,0.933,1.088,2.154,1.985,3.687,2.185,1.534,0.21,3.014-0.43,4.094-1.341,0.021-0.01,0.042-0.021,0.063-0.032,1.018-0.905,1.857-2.235,1.906-3.75,0.049-1.514-0.646-2.818-1.563-3.843-0.02-0.021-0.041-0.042-0.062-0.063-1.559-1.636-2.918-2.965-4.094-4.062-0.01-0.011-0.021-0.021-0.031-0.032-1.741-1.582-3.356-2.893-4.875-3.906-1.063-0.744-2.266-1.24-3.688-1.156zm-45.968,5.406c-1.526,0.055-2.976,0.36-4.188,1.344-1.228,0.997-1.844,2.662-1.844,4.156-0.001,0.042-0.001,0.084,0,0.125,0.151,2.794,0.269,5.661,0.344,8.534,0.055,2.48,0.026,4.61,0,6.75h-23.969c-1.377,0-2.914,0.51-3.906,1.62-0.992,1.12-1.323,2.52-1.375,3.91-0.05,1.4,0.145,2.82,1.063,4.03,0.917,1.21,2.503,1.78,3.875,1.78h23.5c-1.118,10.52-4.697,19.55-10.969,27.16-4.045,4.91-9.235,9.18-15.625,12.78-1.585,0.89-2.932,2.22-3.469,3.97-0.472,1.53-0.261,3.32,0.563,4.72,0.003,0-0.004,0.02,0,0.03,0.023,0.04,0.037,0.08,0.062,0.12l0.063-0.03c0.814,1.38,2.219,2.37,3.718,2.72,1.664,0.38,3.407,0.04,4.938-0.78,0.032-0.02,0.063-0.04,0.094-0.06,10.928-6.45,19.303-14.87,24.937-25.19h0.031c3.976-7.36,6.576-15.91,8-25.44h20.688c0.9846,0,0.952,0.2,0.875,0.09,0.0205,0.03,0.0413,0.05,0.0625,0.07-0.1075-0.15,0.1515,0.24,0.0937,1.28-0.6844,9.66-1.2515,16.24-1.625,19.75v0.03c-0.5275,5.5-1.1278,9.6-1.75,12.09-0.76,2.76-1.7441,4.35-2.5937,5.07-0.021,0.01-0.0418,0.02-0.0625,0.03-0.8088,0.72-2.0336,1.22-4.125,1.22h-6.5309c-1.748,0-3.534,0.5-4.782,1.75-1.062,1.06-1.625,2.63-1.625,4.09-0.003,0.07-0.003,0.15,0,0.22,0.123,1.47,0.862,2.86,1.907,3.84l-0.032,0.03c0.027,0.03,0.067,0.04,0.094,0.07,0.011,0.01,0.021,0.02,0.031,0.03,1.262,1.19,3.032,1.75,4.75,1.75l7.4066,0.12h0.0313c6.2648,0,11.418-2.61,14.281-7.53h0.0313c2.1206-3.69,3.5126-9.5,4.7496-17.94,0.011-0.02,0.022-0.04,0.032-0.06,0.462-3.93,0.916-8.88,1.375-14.84,0.459-5.13,0.702-8.88,0.781-11.41,0.184-3.32-0.967-6.4-3.406-8.44v-0.03c-2.262-1.85-5.2882-2.62-8.7191-2.62h-21.188c0.036-1.59,0.094-3.12,0.094-4.88,0-1.65-0.049-4.14-0.125-7.498v-3.031c0.002-0.062,0.002-0.125,0-0.187-0.11-1.546-0.755-3.154-2.062-4.157-1.29-0.989-2.871-1.245-4.438-1.156h-0.062zm37.406,2.094c-1.368,0.121-2.581,0.835-3.4689,1.781-0.0431,0.04-0.0848,0.082-0.125,0.125-0.8348,1.014-1.4285,2.481-1.1875,3.938,0.2411,1.457,1.1663,2.537,2.1874,3.343,1.792,1.498,3.121,2.698,4.156,3.658,0.011,0.01,0.021,0.02,0.032,0.03,1.298,1.15,2.297,2.17,3,3.06,0.02,0.03,0.041,0.07,0.062,0.1,0.934,1.09,2.159,2,3.688,2.21,1.529,0.22,3.029-0.42,4.125-1.34,0.021-0.01,0.041-0.02,0.062-0.03,1.026-0.91,1.875-2.27,1.906-3.78,0.032-1.52-0.683-2.8-1.593-3.814-0.021-0.021-0.042-0.042-0.063-0.062-1.547-1.625-2.9-2.98-4.094-4.094-0.01-0.011-0.02-0.021-0.031-0.031-1.727-1.57-3.347-2.912-4.937-4-1.059-0.725-2.349-1.215-3.719-1.094z" fill="url(#m)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M-61.025,286.89c-3.782,0-7.29,2.17-8.968,5.56-2.725,5.45-4,11.45-4,17.66,0.002,2.9,1.311,5.59,3.437,7.47l1,6.03c1.254,7.88,2.268,14.86,3.125,21.22,0.957,7.16,1.294,11.61,1.406,14.46-0.503,0.33-0.753,0.54-2.125,1.1-3.979,1.6-8.262,2.44-13.218,2.44-4.428,0-6.878-0.95-7.75-1.57-1.089-0.76-0.594,0.48-0.594-1.93,0-1.31,0.182-2.78,0.656-4.47,0.403-1.41,1.087-3.24,2.094-5.41,0.778-1.65,1.762-3.55,3.031-5.84,2.602-4.68,0.949-10.72-3.656-13.44l-1.688-1.03c-2.324-1.4-5.099-1.77-7.718-1.06-2.619,0.7-4.847,2.43-6.157,4.81-1.99,3.6-3.62,6.73-4.91,9.56-1.75,3.83-3.1,7.47-4.03,10.97-1.11,4.12-1.68,8.22-1.68,12.28,0,7.05,2.85,13.72,7.9,18.47v0.62l2.81,1.88c6.143,4.11,13.616,5.53,21.785,5.53,6.2,0,11.614-0.55,16.532-1.75h0.437l0.656-0.19,0.125-0.03,0.063-0.03c6.188-1.71,11.792-4.84,15.781-9.78l0.031-0.03c4.435-5.56,6.282-12.64,6.282-20.03,0-4.72-0.518-10.68-1.407-18.38-0.561-4.86-1.368-11.12-2.5-18.75v-0.03c-0.239-1.59-0.264-1.84-0.375-2.59,0.733-1.01,1.337-2.11,1.657-3.35,0.59-2.28,1.016-4.12,1.281-5.81,0.335-2.15,0.406-4.35,0.406-7.09-0.003-3.79-2.172-7.29-5.562-8.97-0.744-0.37-1.617-0.81-2.657-1.31-0.48-0.24-0.886-0.39-1.343-0.6-1.431-3.8-4.989-6.58-9.188-6.59h-0.969zm2.5,11.25h0.032c-0.007,0.01-0.025,0.02-0.032,0.03v-0.03zm1,6.37c0.79,0.35,1.549,0.68,2.407,1.1h0.031v0.03c0.334,0.16,0.597,0.28,0.906,0.44-0.038,1.29-0.061,2.76-0.156,3.37-0.103,0.66-0.436,1.97-0.719,3.16l-3.187,1.09-0.157-0.97-0.531-3.09-2.094-1.13c0.071-1.36,0.191-2.7,0.438-3.97l3.062-0.03zm4.938,2.35h0.031v0.03c-0.013-0.01-0.018-0.03-0.031-0.03zm-47.344,59.28c1.065,2.28,2.593,4.26,4.438,5.56,3.942,2.78,8.992,3.94,15.125,3.94,6.437,0,12.488-1.14,18.031-3.38,3.08-1.24,5.327-2.45,7.312-4.37,0.407-0.4,0.767-1.03,1.157-1.53-0.585,2.41-1.505,4.43-2.782,6.03-1.939,2.4-4.919,4.22-9.062,5.4l-0.188,0.07c-3.645,0.99-8.417,1.53-14.375,1.53-6.53,0-11.23-1.15-14.437-3.22l-0.219-0.16c-3.183-2.14-4.887-5.42-5-9.87z" fill="#000"/>
<path d="M-63.656,295.12c-1.366,0.01-2.613,0.78-3.219,2-2.252,4.51-3.344,9.49-3.344,14.82,0.001,1.34,0.748,2.57,1.938,3.18l0.937,0.5,1.469,8.76c1.262,7.93,2.292,15.03,3.156,21.43,1.113,8.33,1.656,14.29,1.657,17.41-0.001,0.55-0.107,1.01-1.032,1.9-0.924,0.9-2.69,1.99-5.281,3.04-4.761,1.91-9.928,2.9-15.625,2.9-5.28,0-9.03-1.05-11.438-2.75-2.331-1.64-3.312-3.57-3.312-7.15,0-1.96,0.297-4.01,0.906-6.19,0.512-1.79,1.306-3.94,2.438-6.38,0.868-1.84,1.945-3.88,3.25-6.25,0.948-1.7,0.365-3.85-1.313-4.84l-1.719-1.03c-0.836-0.5-1.842-0.65-2.785-0.39-0.943,0.25-1.743,0.88-2.215,1.73-1.942,3.52-3.502,6.53-4.692,9.13-1.62,3.55-2.83,6.87-3.65,9.97-0.98,3.63-1.47,7.17-1.47,10.62,0,6.5,2.76,12.18,7.88,15.63-0.01,0.01-0.01,0.02,0,0.03,4.669,3.13,10.796,4.44,18.214,4.43,6.324,0,11.633-0.54,16.062-1.74h0.032c5.224-1.45,9.549-3.97,12.531-7.66,3.326-4.17,4.875-9.67,4.875-16.03,0-4.25-0.468-10.07-1.344-17.66-0.55-4.76-1.375-10.95-2.5-18.53-0.547-3.63-0.617-4.16-0.875-6,1.142-0.39,2.01-1.33,2.313-2.5,0.563-2.18,0.948-3.89,1.156-5.22,0.239-1.53,0.312-3.47,0.312-6.09-0.001-1.37-0.776-2.62-2-3.22-0.754-0.38-1.612-0.81-2.594-1.28-1.378-0.67-2.668-1.25-3.874-1.75h-0.032c0.001-0.03-0.201-0.54-0.312-1.6-0.193-1.82-1.728-3.21-3.563-3.22h-0.937z" transform="translate(-1.63, -6.1)" stroke="#510000" stroke-width="10" fill="none"/>
<path transform="translate(-1.63, -6.1)" d="M-63.656,295.12c-1.366,0.01-2.613,0.78-3.219,2-2.252,4.51-3.344,9.49-3.344,14.82,0.001,1.34,0.748,2.57,1.938,3.18l0.937,0.5,1.469,8.76c1.262,7.93,2.292,15.03,3.156,21.43,1.113,8.33,1.656,14.29,1.657,17.41-0.001,0.55-0.107,1.01-1.032,1.9-0.924,0.9-2.69,1.99-5.281,3.04-4.761,1.91-9.928,2.9-15.625,2.9-5.28,0-9.03-1.05-11.438-2.75-2.331-1.64-3.312-3.57-3.312-7.15,0-1.96,0.297-4.01,0.906-6.19,0.512-1.79,1.306-3.94,2.438-6.38,0.868-1.84,1.945-3.88,3.25-6.25,0.948-1.7,0.365-3.85-1.313-4.84l-1.719-1.03c-0.836-0.5-1.842-0.65-2.785-0.39-0.943,0.25-1.743,0.88-2.215,1.73-1.942,3.52-3.502,6.53-4.692,9.13-1.62,3.55-2.83,6.87-3.65,9.97-0.98,3.63-1.47,7.17-1.47,10.62,0,6.5,2.76,12.18,7.88,15.63-0.01,0.01-0.01,0.02,0,0.03,4.669,3.13,10.796,4.44,18.214,4.43,6.324,0,11.633-0.54,16.062-1.74h0.032c5.224-1.45,9.549-3.97,12.531-7.66,3.326-4.17,4.875-9.67,4.875-16.03,0-4.25-0.468-10.07-1.344-17.66-0.55-4.76-1.375-10.95-2.5-18.53-0.547-3.63-0.617-4.16-0.875-6,1.142-0.39,2.01-1.33,2.313-2.5,0.563-2.18,0.948-3.89,1.156-5.22,0.239-1.53,0.312-3.47,0.312-6.09-0.001-1.37-0.776-2.62-2-3.22-0.754-0.38-1.612-0.81-2.594-1.28-1.378-0.67-2.668-1.25-3.874-1.75h-0.032c0.001-0.03-0.201-0.54-0.312-1.6-0.193-1.82-1.728-3.21-3.563-3.22h-0.937z" fill="url(#n)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M84.844,406.06c-1.134,0.12-2.236,0.56-3.25,1.25-2.029,1.38-3.196,3.74-3.063,6.19,0.174,5.03,0.324,11.84,0.5,18.62h-3.937l-1.406-1.24c0.945-0.89,1.658-2.03,2-3.32,0.671-2.53-0.141-5.28-2.094-7.03l-0.094-0.09-0.125-0.1-6.156-5.06c-1.462-1.3-3.212-1.76-5.157-1.56-0.608-0.95-1.353-1.81-2.374-2.38l-7.032-4.25c-1.169-0.73-2.433-1.03-3.812-1h-0.032c-3.379,0.1-6.28,2.69-6.75,6.03,0.078-0.54-0.176,0.75-0.812,2.35-0.636,1.59-1.628,3.76-2.906,6.44-2.398,5.01-6.437,11.81-12.094,20.06-1.892,2.72-1.551,6.46,0.75,8.84l0.031,0.03,0.031,0.04,1.094,1.09c1.083,1.09,2.488,1.66,3.938,1.87-0.042,8.6-0.07,18.19-0.219,22.94v0.03c-0.029,1.03,0.291,1.99,0.687,2.91-0.999,3.22-2.13,5.28-2.093,5.25-1.466,1.29-3.047,2.89-3.907,5.47-1.038,3.11-0.225,6.71,2.126,9.06,2.968,2.97,8.227,3.76,11.937,1.91,1.539-0.77,2.807-1.85,3.875-3.07,2.082,1.47,4.625,1.82,6.625,1.54,1.928-0.28,3.638-1.36,5.094-2.69,0.346,0.2,0.676,0.45,1.031,0.59-0.108,1.44,0.166,2.93,0.969,4.25l0.062,0.09,0.063,0.1,0.718,1.09c2.045,3.15,6.379,4.08,9.563,2.1,7.964-4.74,14.225-11.13,18.937-18.88v2.44c0,3.22,0.735,6.66,3.094,9.47,2.405,2.86,5.991,4.2,9.282,4.4h0.252c0.12,0.01,0.23,0.01,0.34,0h11.06c5.8,0,11.08-4.17,13.29-9.31,0.95-2.06,0.78-4.28-0.35-6.25-0.64-1.12-1.61-1.95-2.72-2.56-0.19-2.55-0.31-5.72-0.31-9.84,0.02-1.89-0.73-3.64-2.06-4.97-1.34-1.34-3.18-2.09-5.06-2.07h-1.72c-2.79-0.03-5.22,1.65-6.35,4.07v-22.94h12.13c2.86,0.05,5.41-1.69,6.5-4.35,1.09-2.65,0.44-5.73-1.63-7.71l-6.03-6.07c-0.43-0.43-0.98-0.69-1.5-1-0.14-2.99-1.36-5.84-3.06-7.96-3-3.75-7.01-5.8-12.69-8.38-0.143-0.07-0.294-0.04-0.436-0.09-0.658-0.82-1.432-1.58-2.406-2.03l-0.126-0.07-0.124-0.03-8.688-3.62c-1.113-0.52-2.272-0.72-3.406-0.6zm-5.813,42.6c-0.196,5.17-0.706,10.05-1.75,14.46-0.072-4-0.164-8.36-0.187-14.12,0.509-0.04,0.999-0.09,1.5-0.25l0.062-0.03h0.063c0.085-0.03,0.225-0.04,0.312-0.06z" fill="#000"/>
<path d="M44.734,408.22c-0.301,0.01-0.552,0.23-0.593,0.53-0.21,1.47-1.637,5.13-4.282,10.66-2.628,5.5-6.819,12.48-12.593,20.91-0.169,0.24-0.143,0.56,0.062,0.78l1.094,1.09c0.229,0.23,0.598,0.25,0.844,0.03,1.939-1.74,3.586-3.5,5.062-5.25-0.009,15.45-0.083,27.42-0.312,34.75-0.007,0.22,0.104,0.43,0.291,0.54,0.187,0.12,0.421,0.13,0.615,0.02l4.687-2.53c0.204-0.1,0.336-0.3,0.344-0.53v-2.62h20.438v3.97c-0.06-0.03-0.097-0.07-0.157-0.1-0.286-0.15-0.641-0.05-0.812,0.22l-0.719,1.09c-0.152,0.24-0.126,0.55,0.063,0.75,1.673,1.92,2.84,3.45,3.531,4.6,0.687,1.14,1.345,2.7,1.937,4.59,0.325,1.04,0.759,1.82,1.469,2.22s1.604,0.24,2.375-0.25c0.324-0.21,0.57-0.47,0.813-0.75-2.695,3.53-5.749,6.78-9.313,9.69-0.253,0.19-0.32,0.54-0.156,0.81l0.719,1.09c0.183,0.29,0.558,0.37,0.843,0.19,17.903-10.64,26.682-29.9,26.344-57.4h2.375v46.37c0,2.26,0.507,4.08,1.594,5.38,1.086,1.29,2.737,1.99,4.781,2.12h11.252c3.35,0,5.9-1.92,7.4-5.44,0.09-0.18,0.08-0.39-0.02-0.56-0.1-0.18-0.28-0.29-0.48-0.31-0.95-0.11-1.59-0.47-2.09-1.13s-0.82-1.66-0.94-3.03c-0.24-2.87-0.37-6.56-0.37-11.12,0-0.17-0.07-0.33-0.18-0.45-0.12-0.12-0.28-0.18-0.45-0.18h-1.81c-0.33,0-0.61,0.26-0.62,0.6-0.25,5.29-0.55,9.38-0.91,12.25-0.17,1.37-0.53,2.34-1,2.93s-1,0.85-1.81,0.85h-4.03c-1.827,0.22-2.995-0.07-3.661-0.78-0.666-0.72-0.949-2.02-0.718-3.97,0.001-0.02,0.001-0.05,0-0.07v-43.46h18.529c0.26,0,0.49-0.15,0.58-0.39,0.1-0.23,0.04-0.5-0.14-0.68l-6.13-6.15c-0.11-0.12-0.27-0.19-0.43-0.19-0.17,0-0.32,0.07-0.44,0.19l-4.16,4.15h-16.122v-19.31l2.969-1.97c0.179-0.13,0.278-0.34,0.258-0.56-0.019-0.22-0.153-0.41-0.352-0.5l-8.687-3.63c-0.197-0.09-0.427-0.07-0.606,0.05s-0.281,0.33-0.269,0.55c0.235,6.82,0.482,15.34,0.718,25.37h-12c-0.238,0-0.457,0.13-0.562,0.35l-5.188-4.53c-0.128-0.11-0.292-0.16-0.457-0.14-0.164,0.02-0.315,0.1-0.418,0.23l-2.718,3.37h-7.094c2.095-2.77,3.962-5.22,5.312-6.84,1.548-1.86,2.694-2.98,3.094-3.22,1.15-0.69,2.306-1.15,3.438-1.37,0.226-0.05,0.406-0.22,0.465-0.45,0.06-0.22-0.012-0.46-0.184-0.62l-6.156-5.06c-0.13-0.11-0.302-0.17-0.474-0.15-0.173,0.02-0.33,0.11-0.433,0.25l-2.343,3h-8.688c0.965-1.64,1.886-3,2.688-3.85,0.961-1.02,1.763-1.4,2.343-1.4,0.284,0,0.534-0.19,0.608-0.46s-0.048-0.56-0.295-0.7l-7.219-4.34c-0.103-0.07-0.222-0.1-0.344-0.1zm48.094,5.78c-0.154,0.01-0.299,0.08-0.406,0.19l-1.094,1.09c-0.119,0.13-0.179,0.3-0.168,0.47,0.012,0.17,0.096,0.33,0.231,0.44,3.795,3.08,6.048,6.14,6.843,9.09,0.432,1.61,0.907,2.79,1.5,3.6,0.296,0.4,0.626,0.72,1.036,0.9,0.4,0.19,0.88,0.23,1.31,0.1,1.44-0.44,2.42-1.73,2.94-3.53,0.54-1.9-0.04-3.85-1.57-5.75-1.59-1.99-5.003-4.11-10.341-6.53-0.087-0.05-0.184-0.07-0.281-0.07zm-47.75,8.13h9.531l-5.562,11.4h-8.094l-2.562-1.71c2.227-2.93,4.462-6.15,6.687-9.69zm24.438,13.81l2.281,2.28c0.162,0.17,0.404,0.23,0.625,0.16,2.098-0.7,4.468-1.06,7.062-1.06h1.907c0.622,18.59-3.482,33.69-12.282,45.37,0.239-0.33,0.435-0.72,0.563-1.12,0.278-0.89,0.285-1.89,0.094-3.04-0.221-1.32-1.188-2.59-2.782-3.9-1.344-1.11-3.247-2.29-5.531-3.5l4.875-2.06c0.236-0.11,0.386-0.34,0.375-0.6-0.238-6.18-0.342-16.38-0.344-30.37l2.969-2c0.072-0.04,0.136-0.1,0.188-0.16zm-29.563,0.66h7.438v11.75h-7.438v-11.75zm13.031,0h7.407v11.75h-7.407v-11.75zm-13.031,14.81h7.438v12.12h-7.438v-12.12zm13.031,0h7.407v12.12h-7.407v-12.12zm-1.812,20.09c-0.089,0.02-0.175,0.05-0.25,0.1l-1.094,0.72c-0.282,0.18-0.365,0.55-0.187,0.84,0.953,1.67,1.786,3.33,2.5,5,0.7,1.63,1.238,3.49,1.593,5.62,0.193,1.16,0.497,2.05,1.157,2.57,0.659,0.52,1.595,0.48,2.437,0.06,1.68-0.84,2.471-2.67,2.344-5.09-0.072-1.37-0.845-2.79-2.188-4.38s-3.29-3.37-5.843-5.31c-0.133-0.11-0.302-0.15-0.469-0.13zm-8.656,1.44c-0.144,0.02-0.276,0.08-0.375,0.19l-0.719,0.72c-0.167,0.17-0.216,0.43-0.125,0.65,0.466,1.17,0.894,2.59,1.25,4.25,0.345,1.62,0.431,3.56,0.312,5.82-0.064,1.22,0.113,2.2,0.657,2.9,0.543,0.7,1.46,0.95,2.437,0.81,0.993-0.14,1.811-0.67,2.344-1.5,0.532-0.82,0.811-1.91,0.875-3.25,0.137-2.88-2.012-6.26-6.156-10.4-0.13-0.14-0.314-0.21-0.5-0.19zm-6.875,0.72c-0.256,0.04-0.455,0.24-0.5,0.5-1.192,5.24-2.85,8.69-4.719,10.34-1.067,0.94-1.765,1.8-2.063,2.69s-0.075,1.86,0.594,2.53c1.23,1.23,2.914,1.5,4.531,0.69,1.597-0.8,2.767-2.25,3.532-4.16,0.816-2.04,1.023-5.81,0.781-11.62-0.016-0.27-0.206-0.5-0.469-0.56l-1.437-0.38c-0.081-0.03-0.166-0.04-0.25-0.03z" stroke="#003c00" stroke-width="10" fill="none"/>
<path d="M44.734,408.22c-0.301,0.01-0.552,0.23-0.593,0.53-0.21,1.47-1.637,5.13-4.282,10.66-2.628,5.5-6.819,12.48-12.593,20.91-0.169,0.24-0.143,0.56,0.062,0.78l1.094,1.09c0.229,0.23,0.598,0.25,0.844,0.03,1.939-1.74,3.586-3.5,5.062-5.25-0.009,15.45-0.083,27.42-0.312,34.75-0.007,0.22,0.104,0.43,0.291,0.54,0.187,0.12,0.421,0.13,0.615,0.02l4.687-2.53c0.204-0.1,0.336-0.3,0.344-0.53v-2.62h20.438v3.97c-0.06-0.03-0.097-0.07-0.157-0.1-0.286-0.15-0.641-0.05-0.812,0.22l-0.719,1.09c-0.152,0.24-0.126,0.55,0.063,0.75,1.673,1.92,2.84,3.45,3.531,4.6,0.687,1.14,1.345,2.7,1.937,4.59,0.325,1.04,0.759,1.82,1.469,2.22s1.604,0.24,2.375-0.25c0.324-0.21,0.57-0.47,0.813-0.75-2.695,3.53-5.749,6.78-9.313,9.69-0.253,0.19-0.32,0.54-0.156,0.81l0.719,1.09c0.183,0.29,0.558,0.37,0.843,0.19,17.903-10.64,26.682-29.9,26.344-57.4h2.375v46.37c0,2.26,0.507,4.08,1.594,5.38,1.086,1.29,2.737,1.99,4.781,2.12h11.252c3.35,0,5.9-1.92,7.4-5.44,0.09-0.18,0.08-0.39-0.02-0.56-0.1-0.18-0.28-0.29-0.48-0.31-0.95-0.11-1.59-0.47-2.09-1.13s-0.82-1.66-0.94-3.03c-0.24-2.87-0.37-6.56-0.37-11.12,0-0.17-0.07-0.33-0.18-0.45-0.12-0.12-0.28-0.18-0.45-0.18h-1.81c-0.33,0-0.61,0.26-0.62,0.6-0.25,5.29-0.55,9.38-0.91,12.25-0.17,1.37-0.53,2.34-1,2.93s-1,0.85-1.81,0.85h-4.03c-1.827,0.22-2.995-0.07-3.661-0.78-0.666-0.72-0.949-2.02-0.718-3.97,0.001-0.02,0.001-0.05,0-0.07v-43.46h18.529c0.26,0,0.49-0.15,0.58-0.39,0.1-0.23,0.04-0.5-0.14-0.68l-6.13-6.15c-0.11-0.12-0.27-0.19-0.43-0.19-0.17,0-0.32,0.07-0.44,0.19l-4.16,4.15h-16.122v-19.31l2.969-1.97c0.179-0.13,0.278-0.34,0.258-0.56-0.019-0.22-0.153-0.41-0.352-0.5l-8.687-3.63c-0.197-0.09-0.427-0.07-0.606,0.05s-0.281,0.33-0.269,0.55c0.235,6.82,0.482,15.34,0.718,25.37h-12c-0.238,0-0.457,0.13-0.562,0.35l-5.188-4.53c-0.128-0.11-0.292-0.16-0.457-0.14-0.164,0.02-0.315,0.1-0.418,0.23l-2.718,3.37h-7.094c2.095-2.77,3.962-5.22,5.312-6.84,1.548-1.86,2.694-2.98,3.094-3.22,1.15-0.69,2.306-1.15,3.438-1.37,0.226-0.05,0.406-0.22,0.465-0.45,0.06-0.22-0.012-0.46-0.184-0.62l-6.156-5.06c-0.13-0.11-0.302-0.17-0.474-0.15-0.173,0.02-0.33,0.11-0.433,0.25l-2.343,3h-8.688c0.965-1.64,1.886-3,2.688-3.85,0.961-1.02,1.763-1.4,2.343-1.4,0.284,0,0.534-0.19,0.608-0.46s-0.048-0.56-0.295-0.7l-7.219-4.34c-0.103-0.07-0.222-0.1-0.344-0.1zm48.094,5.78c-0.154,0.01-0.299,0.08-0.406,0.19l-1.094,1.09c-0.119,0.13-0.179,0.3-0.168,0.47,0.012,0.17,0.096,0.33,0.231,0.44,3.795,3.08,6.048,6.14,6.843,9.09,0.432,1.61,0.907,2.79,1.5,3.6,0.296,0.4,0.626,0.72,1.036,0.9,0.4,0.19,0.88,0.23,1.31,0.1,1.44-0.44,2.42-1.73,2.94-3.53,0.54-1.9-0.04-3.85-1.57-5.75-1.59-1.99-5.003-4.11-10.341-6.53-0.087-0.05-0.184-0.07-0.281-0.07zm-47.75,8.13h9.531l-5.562,11.4h-8.094l-2.562-1.71c2.227-2.93,4.462-6.15,6.687-9.69zm24.438,13.81l2.281,2.28c0.162,0.17,0.404,0.23,0.625,0.16,2.098-0.7,4.468-1.06,7.062-1.06h1.907c0.622,18.59-3.482,33.69-12.282,45.37,0.239-0.33,0.435-0.72,0.563-1.12,0.278-0.89,0.285-1.89,0.094-3.04-0.221-1.32-1.188-2.59-2.782-3.9-1.344-1.11-3.247-2.29-5.531-3.5l4.875-2.06c0.236-0.11,0.386-0.34,0.375-0.6-0.238-6.18-0.342-16.38-0.344-30.37l2.969-2c0.072-0.04,0.136-0.1,0.188-0.16zm-29.563,0.66h7.438v11.75h-7.438v-11.75zm13.031,0h7.407v11.75h-7.407v-11.75zm-13.031,14.81h7.438v12.12h-7.438v-12.12zm13.031,0h7.407v12.12h-7.407v-12.12zm-1.812,20.09c-0.089,0.02-0.175,0.05-0.25,0.1l-1.094,0.72c-0.282,0.18-0.365,0.55-0.187,0.84,0.953,1.67,1.786,3.33,2.5,5,0.7,1.63,1.238,3.49,1.593,5.62,0.193,1.16,0.497,2.05,1.157,2.57,0.659,0.52,1.595,0.48,2.437,0.06,1.68-0.84,2.471-2.67,2.344-5.09-0.072-1.37-0.845-2.79-2.188-4.38s-3.29-3.37-5.843-5.31c-0.133-0.11-0.302-0.15-0.469-0.13zm-8.656,1.44c-0.144,0.02-0.276,0.08-0.375,0.19l-0.719,0.72c-0.167,0.17-0.216,0.43-0.125,0.65,0.466,1.17,0.894,2.59,1.25,4.25,0.345,1.62,0.431,3.56,0.312,5.82-0.064,1.22,0.113,2.2,0.657,2.9,0.543,0.7,1.46,0.95,2.437,0.81,0.993-0.14,1.811-0.67,2.344-1.5,0.532-0.82,0.811-1.91,0.875-3.25,0.137-2.88-2.012-6.26-6.156-10.4-0.13-0.14-0.314-0.21-0.5-0.19zm-6.875,0.72c-0.256,0.04-0.455,0.24-0.5,0.5-1.192,5.24-2.85,8.69-4.719,10.34-1.067,0.94-1.765,1.8-2.063,2.69s-0.075,1.86,0.594,2.53c1.23,1.23,2.914,1.5,4.531,0.69,1.597-0.8,2.767-2.25,3.532-4.16,0.816-2.04,1.023-5.81,0.781-11.62-0.016-0.27-0.206-0.5-0.469-0.56l-1.437-0.38c-0.081-0.03-0.166-0.04-0.25-0.03z" fill="url(#o)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M208.28,99.312c-9.25,0.001-16.95,1.548-22.87,5.718-5.68,4-9.54,10.17-11.6,17.53l-1.87,6.78,6.9,1.25,7.72,1.41c-0.33,0.15-0.77,0.25-1.09,0.41-4.18,2.02-7.7,5.14-10.09,9.06-2.4,3.92-3.57,8.51-3.57,13.22,0,7.06,2.76,13.73,7.81,18.5,5.22,4.91,12.4,7.19,20.29,7.19,4.61,0,9.11-0.88,13.28-2.66,1.26-0.54,2.42-1.31,3.62-2l1.25,3.19h31.69l-4.47-9.19c-1.33-2.73-2.17-5.18-2.59-7.25-0.38-1.87-0.69-5.87-0.69-11.41l0.16-20.12v-0.06c0-7.95-0.41-13.62-3.1-18.54-2.31-4.23-6.15-7.42-10.78-9.65-5.31-2.57-11.79-3.377-20-3.378zm0,12.808c7.2,0.01,12.2,1.02,14.44,2.1,2.92,1.41,4.36,2.86,5.12,4.25,0.39,0.7,1.53,5.34,1.54,12.41l-0.19,20.12v0.06c0,5.95,0.24,10.39,0.97,13.97,0.07,0.36,0.22,0.73,0.31,1.09h-3.41c-0.06-0.22-0.09-0.26-0.15-0.5-0.31-1.08-0.44-1.65-0.75-2.5l-2.6-6.9c0.2-0.49,0.54-0.9,0.69-1.41,0.94-3.07,0.94-5.84,0.94-10.22v-16.43c0-4.38-1.17-8.91-4.69-11.94-3.86-3.33-8.22-3.69-13.44-3.69-3.91,0-7.68,0.76-10.87,3-1.72,1.21-3.04,2.73-4.13,4.44l-3.03-0.56c1.08-1.63,2.34-2.91,3.75-3.91,2.69-1.89,7.78-3.37,15.5-3.38zm-1.22,13.22c1.61,0,2.63,0.2,3.5,0.38-2.14,0.54-5.01,1.15-8.03,1.75,0.47-0.85,0.89-1.39,1-1.47,0.26-0.18,1.3-0.66,3.53-0.66zm-13.47,17.66c-2.66,2.57-4.5,6.2-4.5,9.91,0,4.01,1.78,8.05,4.63,10.84,1.89,1.84,4.29,3.08,6.84,3.78-0.22,0.01-0.42,0.06-0.65,0.06-5.57,0-8.97-1.36-11.5-3.75-2.69-2.53-3.82-5.06-3.82-9.15,0-2.67,0.59-4.71,1.72-6.57,1.14-1.86,2.57-3.13,4.75-4.18,0.61-0.3,1.67-0.63,2.53-0.94zm18.5,6.44c-0.03,0.33-0.1,1.75-0.09,1.72v0.03l-0.03,0.03c-0.26,0.88-0.62,1.51-1.78,2.37-1.93,1.4-3.38,1.82-5.1,1.82-1.44,0-1.81-0.21-2.43-0.82-0.76-0.74-0.78-0.86-0.78-1.68,0-0.51-0.31-0.18,0.68-0.88-0.33,0.22,2.55-1,7.06-1.97,1.09-0.23,1.51-0.41,2.47-0.62z" fill="#000"/>
<path d="M191.4,122.86l-15.68-2.83c1.76-6.31,4.8-10.99,9.1-14.02,4.31-3.04,10.7-4.55,19.19-4.55,7.71,0,13.45,0.91,17.22,2.73,3.77,1.83,6.43,4.14,7.97,6.95,1.53,2.81,2.3,7.97,2.3,15.47l-0.18,20.17c0,5.74,0.28,9.98,0.83,12.7,0.55,2.73,1.59,5.65,3.1,8.77h-17.09c-0.45-1.15-1.01-2.85-1.66-5.11-0.29-1.02-0.5-1.7-0.62-2.03-2.95,2.87-6.11,5.03-9.47,6.46-3.36,1.44-6.95,2.15-10.76,2.15-6.73,0-12.03-1.82-15.9-5.47-3.88-3.65-5.81-8.26-5.81-13.84,0-3.69,0.88-6.98,2.64-9.87,1.77-2.89,4.24-5.1,7.41-6.64,3.18-1.54,7.76-2.88,13.75-4.03,8.08-1.52,13.67-2.93,16.79-4.24v-1.73c0-3.32-0.82-5.68-2.46-7.1-1.64-1.41-4.74-2.12-9.29-2.12-3.07,0-5.47,0.6-7.19,1.81-1.73,1.21-3.12,3.33-4.19,6.37zm23.13,14.02c-2.22,0.74-5.72,1.62-10.52,2.65-4.8,1.02-7.93,2.03-9.41,3.01-2.25,1.6-3.38,3.63-3.38,6.09,0,2.42,0.9,4.51,2.71,6.27,1.8,1.76,4.1,2.65,6.88,2.65,3.12,0,6.09-1.03,8.92-3.08,2.09-1.56,3.47-3.46,4.12-5.72,0.45-1.48,0.68-4.28,0.68-8.42v-3.45z" stroke="#500050" stroke-width="10" fill="none"/>
<path d="M191.4,122.86l-15.68-2.83c1.76-6.31,4.8-10.99,9.1-14.02,4.31-3.04,10.7-4.55,19.19-4.55,7.71,0,13.45,0.91,17.22,2.73,3.77,1.83,6.43,4.14,7.97,6.95,1.53,2.81,2.3,7.97,2.3,15.47l-0.18,20.17c0,5.74,0.28,9.98,0.83,12.7,0.55,2.73,1.59,5.65,3.1,8.77h-17.09c-0.45-1.15-1.01-2.85-1.66-5.11-0.29-1.02-0.5-1.7-0.62-2.03-2.95,2.87-6.11,5.03-9.47,6.46-3.36,1.44-6.95,2.15-10.76,2.15-6.73,0-12.03-1.82-15.9-5.47-3.88-3.65-5.81-8.26-5.81-13.84,0-3.69,0.88-6.98,2.64-9.87,1.77-2.89,4.24-5.1,7.41-6.64,3.18-1.54,7.76-2.88,13.75-4.03,8.08-1.52,13.67-2.93,16.79-4.24v-1.73c0-3.32-0.82-5.68-2.46-7.1-1.64-1.41-4.74-2.12-9.29-2.12-3.07,0-5.47,0.6-7.19,1.81-1.73,1.21-3.12,3.33-4.19,6.37zm23.13,14.02c-2.22,0.74-5.72,1.62-10.52,2.65-4.8,1.02-7.93,2.03-9.41,3.01-2.25,1.6-3.38,3.63-3.38,6.09,0,2.42,0.9,4.51,2.71,6.27,1.8,1.76,4.1,2.65,6.88,2.65,3.12,0,6.09-1.03,8.92-3.08,2.09-1.56,3.47-3.46,4.12-5.72,0.45-1.48,0.68-4.28,0.68-8.42v-3.45z" fill="url(#p)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 41 KiB

438
imgsrc/mimetypes/djvu.svg Normal file
View File

@ -0,0 +1,438 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="128"
height="128"
id="svg2606"
inkscape:version="0.48.1 "
sodipodi:docname="C:\cygwin\home\mperry\calibre\imgsrc\mimetypes\djvu.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="975"
inkscape:window-height="735"
id="namedview3077"
showgrid="false"
inkscape:zoom="2.0390625"
inkscape:cx="64"
inkscape:cy="64"
inkscape:window-x="298"
inkscape:window-y="122"
inkscape:window-maximized="0"
inkscape:current-layer="svg2606" />
<defs
id="defs2608">
<linearGradient
id="linearGradient10207">
<stop
id="stop10209"
style="stop-color:#a2a2a2;stop-opacity:1"
offset="0" />
<stop
id="stop10211"
style="stop-color:#ffffff;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="96"
y1="104"
x2="88.000198"
y2="96.000198"
id="XMLID_12_"
gradientUnits="userSpaceOnUse">
<stop
id="stop83"
style="stop-color:#888a85;stop-opacity:1"
offset="0" />
<stop
id="stop85"
style="stop-color:#8c8e89;stop-opacity:1"
offset="0.0072" />
<stop
id="stop87"
style="stop-color:#abaca9;stop-opacity:1"
offset="0.0673" />
<stop
id="stop89"
style="stop-color:#c5c6c4;stop-opacity:1"
offset="0.1347" />
<stop
id="stop91"
style="stop-color:#dbdbda;stop-opacity:1"
offset="0.2652576" />
<stop
id="stop93"
style="stop-color:#ebebeb;stop-opacity:1"
offset="0.37646064" />
<stop
id="stop95"
style="stop-color:#f7f7f6;stop-opacity:1"
offset="0.48740286" />
<stop
id="stop97"
style="stop-color:#fdfdfd;stop-opacity:1"
offset="0.6324091" />
<stop
id="stop99"
style="stop-color:#ffffff;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="102"
cy="112.3047"
r="139.55859"
id="XMLID_8_"
gradientUnits="userSpaceOnUse">
<stop
id="stop41"
style="stop-color:#b7b8b9;stop-opacity:1"
offset="0" />
<stop
id="stop47"
style="stop-color:#ececec;stop-opacity:1"
offset="0.18851049" />
<stop
id="stop49"
style="stop-color:#fafafa;stop-opacity:1"
offset="0.25718147" />
<stop
id="stop51"
style="stop-color:#ffffff;stop-opacity:1"
offset="0.30111277" />
<stop
id="stop53"
style="stop-color:#fafafa;stop-opacity:1"
offset="0.53130001" />
<stop
id="stop55"
style="stop-color:#ebecec;stop-opacity:1"
offset="0.84490001" />
<stop
id="stop57"
style="stop-color:#e1e2e3;stop-opacity:1"
offset="1" />
</radialGradient>
<filter
x="-0.19200002"
y="-0.19199999"
width="1.3839999"
height="1.3839999"
color-interpolation-filters="sRGB"
id="filter6697">
<feGaussianBlur
id="feGaussianBlur6699"
stdDeviation="1.9447689" />
</filter>
<clipPath
id="clipPath7084">
<path
d="m 72,88 -32,32 -8,0 0,-40 40,0 0,8 z"
id="path7086"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
</clipPath>
<radialGradient
cx="102"
cy="112.3047"
r="139.55859"
id="radialGradient9437"
xlink:href="#XMLID_8_"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.9996653,2e-6,0.00301608)" />
<linearGradient
x1="98.617439"
y1="106.41443"
x2="91.228737"
y2="99.254974"
id="linearGradient10213"
xlink:href="#linearGradient10207"
gradientUnits="userSpaceOnUse" />
<filter
color-interpolation-filters="sRGB"
id="filter2770">
<feGaussianBlur
id="feGaussianBlur2772"
stdDeviation="2.0786429" />
</filter>
<linearGradient
x1="45.033901"
y1="44.966038"
x2="11.675456"
y2="1.4610662"
id="linearGradient2774"
xlink:href="#linearGradient2545"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0115079,0,0,1.0161106,5.5234761,9.1336546)" />
<linearGradient
x1="25.553648"
y1="34.006008"
x2="0"
y2="34.153435"
id="linearGradient2756"
xlink:href="#linearGradient11545"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0237894,0,0,1.0414051,4.9857129,9.0513362)" />
<linearGradient
x1="40.864098"
y1="40.518246"
x2="33.136433"
y2="32.651588"
id="linearGradient2749"
xlink:href="#linearGradient11663"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0237894,0,0,1.0414051,4.9857129,9.0513362)" />
<linearGradient
x1="45.033901"
y1="44.966038"
x2="11.675456"
y2="1.4610662"
id="linearGradient2543"
xlink:href="#linearGradient2545"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9880039,0,0,0.9757112,0.5252674,0.07904551)" />
<linearGradient
id="linearGradient2545">
<stop
id="stop2547"
style="stop-color:#342679;stop-opacity:1"
offset="0" />
<stop
id="stop2553"
style="stop-color:#7b51ae;stop-opacity:0.96862745"
offset="0.72235626" />
<stop
id="stop2549"
style="stop-color:#d9cce8;stop-opacity:0.96862745"
offset="1" />
</linearGradient>
<linearGradient
x1="25.553648"
y1="34.006008"
x2="0"
y2="34.153435"
id="linearGradient11653"
xlink:href="#linearGradient11545"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.4601868,0,0,0.9728475,2.2307835,28.622031)" />
<linearGradient
id="linearGradient11545">
<stop
id="stop11547"
style="stop-color:#f89c11;stop-opacity:1"
offset="0" />
<stop
id="stop11549"
style="stop-color:#fabf60;stop-opacity:1"
offset="1" />
</linearGradient>
<clipPath
id="clip7">
<path
d="m 10.84375,39.414062 -10.84375,0 0,-10.953124 10.84375,0 0,-28.45703175 35.542969,0 0,32.73437475 -13.058594,13.058594 -22.484375,0 0,-6.382813"
id="path25" />
</clipPath>
<clipPath
id="clip32">
<path
d="m 10.84375,39.414062 -10.84375,0 0,-10.953124 10.84375,0 0,-28.45703175 35.542969,0 0,32.73437475 -13.058594,13.058594 -22.484375,0 0,-6.382813"
id="path100" />
</clipPath>
<clipPath
id="clip48">
<path
d="m 10.84375,39.414062 -10.84375,0 0,-10.953124 10.84375,0 0,-28.45703175 35.542969,0 0,32.73437475 -13.058594,13.058594 -22.484375,0 0,-6.382813"
id="path148" />
</clipPath>
<linearGradient
x1="25.553648"
y1="34.006008"
x2="0"
y2="34.153435"
id="linearGradient2525"
xlink:href="#linearGradient11545"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="40.864098"
y1="40.518246"
x2="33.136433"
y2="32.651588"
id="linearGradient2518"
xlink:href="#linearGradient11663"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient11663">
<stop
id="stop11665"
style="stop-color:#342679;stop-opacity:1"
offset="0" />
<stop
id="stop11667"
style="stop-color:#dacfe4;stop-opacity:1"
offset="1" />
</linearGradient>
</defs>
<metadata
id="metadata2611">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1">
<path
d="m 16,8 0,112 c 0,0 63.15625,0 63.15625,0 l 0.03125,0 c 3e-6,0 11.90625,-9.90625 17.40625,-15.40625 C 102.09375,99.09375 112,87.1875 112,87.1875 L 112,87.15625 112,8 16,8 z"
transform="matrix(1.0416667,0,0,1.0267857,-2.6666667,-1.2142891)"
id="path7865"
style="opacity:0.5;fill:#000000;fill-opacity:1;filter:url(#filter2770)" />
<path
d="M 16.000001,8 16,120 c 0,0 63.146418,0 63.146418,0 L 112,87.14642 112,8 16.000001,8 z"
id="path34"
style="fill:#ffffff;fill-opacity:1" />
<path
d="m 18.000002,9.0000034 c -0.551,0 -1,0.44885 -1,0.999665 l 0,107.9638516 c 0,0.55181 0.449,0.99966 1,0.99966 l 59.171997,0 c 0.263,0 2.76268,0.11813 2.948681,-0.0688 L 110.707,88.094202 C 110.894,87.907264 111,85.40942 111,85.146508 l 0,-75.1468396 c 0,-0.550815 -0.448,-0.999665 -1,-0.999665 l -91.999998,0 z"
id="path59"
style="fill:url(#radialGradient9437);fill-opacity:1" />
<path
d="m 41.879531,115.98249 c 0,0 24.309609,-24.309614 24.309609,-24.309614 0,0 -9.35314,2.913124 -19.60314,2.913124 0,10.25 -4.706469,21.39649 -4.706469,21.39649 z"
transform="translate(40,0)"
clip-path="url(#clipPath7084)"
id="path5540"
style="opacity:0.4;fill:#000000;fill-opacity:1;filter:url(#filter6697)" />
<path
d="m 79.172,120 c 0,0 11.914,-9.914 17.414,-15.414 5.5,-5.5 15.414,-17.414 15.414,-17.414 0,0 -13.75,8.828 -24,8.828 0,10.25 -8.828,24 -8.828,24 z"
id="path14523"
style="fill:url(#linearGradient10213);fill-opacity:1" />
<g
transform="matrix(2.6666667,0,0,2.6666667,-26.364309,-16.219923)"
id="layer1-2">
<g
transform="matrix(1.000026,0,0,0.9968473,-1.2968723e-4,0.405534)"
id="g2454">
<g
transform="matrix(1.0237894,0,0,1.0414051,9.6816161,-27.57005)"
id="g11649">
<rect
width="11.895136"
height="11.569371"
ry="2.7001941e-017"
x="3.1296141"
y="57.056187"
transform="matrix(0.9396926,-0.3420201,0.3420201,0.9396926,0,0)"
id="rect11645"
style="fill:#000000;fill-opacity:1" />
<rect
width="11.895135"
height="11.56937"
ry="2.7001941e-017"
x="2.2307839"
y="56.063431"
transform="matrix(0.9396926,-0.3420201,0.3420201,0.9396926,0,0)"
id="rect11641"
style="fill:url(#linearGradient11653);fill-opacity:1" />
</g>
<g
transform="matrix(1.0237894,0,0,1.0414051,4.9857129,9.0513362)"
clip-path="url(#clip7)"
id="g1139" />
<g
transform="matrix(1.0237894,0,0,1.0414051,0.779179,8.770206)"
id="g11677"
style="fill:#816392;fill-opacity:0.86179516">
<g
transform="translate(4.2261802,1.3274155)"
clip-path="url(#clip32)"
id="g11679"
style="fill:#816392;fill-opacity:0.86179516">
<path
d="m 26.128906,23.296875 c 2.015625,1.242187 4.480469,1.78125 6.523438,0.195313 1.574218,-1.210938 1.84375,-3.335938 1.324218,-5.414063 -0.917968,-3.585937 -4.5625,-7.527344 -4.5625,-7.527344 L 18.972656,-2.15625 c 0,0 -0.136718,-0.171875 -0.433594,-0.261719 -0.414062,-0.117187 -1.035156,0.261719 -0.683593,1.027344 l 10.910156,13.445313 c 0,0 3.265625,3.304687 3.886719,6.472656 0.296875,1.484375 0.214844,2.960937 -0.917969,3.871094 C 30.214844,23.621094 28.289062,22.9375 26.605469,21.863281 24.34375,20.417969 22.429688,18.09375 22.429688,18.09375 L 7.777344,0.679688 7.613281,2.613281 21.472656,19.136719 c 0,0 2.082032,2.566406 4.65625,4.160156"
id="path11681"
style="fill:#816392;fill-opacity:0.86179516;fill-rule:nonzero;stroke:none" />
</g>
<g
transform="translate(4.2261807,1.4535628)"
clip-path="url(#clip48)"
id="g11683"
style="fill:#816392;fill-opacity:0.86179516">
<path
d="m 28.304688,15.984375 c 2.003906,2.550781 0.160156,3.707031 -1.125,3.027344 -1.875,-1 -4.425782,-4.386719 -4.425782,-4.386719 L 15.613281,6.003906 C 15.070312,5.257812 14.429688,5.644531 14.296875,6.09375 c -0.09766,0.347656 0.238281,0.683594 0.238281,0.683594 l 7.191406,8.703125 c 0,0 2.207032,2.601562 3.398438,3.738281 3.5,3.367188 7.761719,0.152344 4.050781,-4.558594 C 27.074219,12 23.867188,8.175781 23.867188,8.175781 L 13.371094,-4.707031 11.859375,-4.839844 11.789062,-4.050781 22.511719,8.71875 c 0,0 3.636719,4.523438 5.792969,7.265625"
id="path11685"
style="fill:#816392;fill-opacity:0.86179516;fill-rule:nonzero;stroke:none" />
</g>
</g>
<g
transform="matrix(1.0237894,0,0,1.0414051,0.659598,7.725718)"
id="g11671">
<g
transform="translate(4.2261802,1.3274155)"
clip-path="url(#clip32)"
id="g3875">
<path
d="m 26.128906,23.296875 c 2.015625,1.242187 4.480469,1.78125 6.523438,0.195313 1.574218,-1.210938 1.84375,-3.335938 1.324218,-5.414063 -0.917968,-3.585937 -4.5625,-7.527344 -4.5625,-7.527344 L 18.972656,-2.15625 c 0,0 -0.136718,-0.171875 -0.433594,-0.261719 -0.414062,-0.117187 -1.035156,0.261719 -0.683593,1.027344 l 10.910156,13.445313 c 0,0 3.265625,3.304687 3.886719,6.472656 0.296875,1.484375 0.214844,2.960937 -0.917969,3.871094 C 30.214844,23.621094 28.289062,22.9375 26.605469,21.863281 24.34375,20.417969 22.429688,18.09375 22.429688,18.09375 L 7.777344,0.679688 7.613281,2.613281 21.472656,19.136719 c 0,0 2.082032,2.566406 4.65625,4.160156"
id="path3877"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</g>
<g
transform="translate(4.2261807,1.4535628)"
clip-path="url(#clip48)"
id="g6171">
<path
d="m 28.304688,15.984375 c 2.003906,2.550781 0.160156,3.707031 -1.125,3.027344 -1.875,-1 -4.425782,-4.386719 -4.425782,-4.386719 L 15.613281,6.003906 C 15.070312,5.257812 14.429688,5.644531 14.296875,6.09375 c -0.09766,0.347656 0.238281,0.683594 0.238281,0.683594 l 7.191406,8.703125 c 0,0 2.207032,2.601562 3.398438,3.738281 3.5,3.367188 7.761719,0.152344 4.050781,-4.558594 C 27.074219,12 23.867188,8.175781 23.867188,8.175781 L 13.371094,-4.707031 11.859375,-4.839844 11.789062,-4.050781 22.511719,8.71875 c 0,0 3.636719,4.523438 5.792969,7.265625"
id="path6173"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</g>
</g>
<text
x="6.855123"
y="40.434292"
transform="scale(0.9915063,1.0085665)"
id="text11553"
xml:space="preserve"
style="font-size:41.30238724px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
x="6.855123"
y="40.434292"
id="tspan11555" /></text>
</g>
</g>
<g
transform="matrix(0.10181328,0,0,0.10181328,23.503861,90.420112)"
id="g8370">
<path
d="m 20.795,23.442 c 41.244,-1.555 81.688,-2.171 104.79,17.582 10.619,27.832 15.009,83.404 4.99,117.209 -18.421,31.11 -62.996,31.504 -109.78,29.303 0,-54.699 0,-109.396 0,-164.094 z m 34.93,134.79 c 11.643,0 23.287,0 34.93,0 16.604,-30.859 22.254,-125.032 -34.93,-105.488 0,35.163 0,70.326 0,105.488 z"
id="path8372"
style="fill-rule:evenodd" />
<path
d="m 165.505,23.442 c 33.267,0 66.533,0 99.8,0 7.251,46.182 16.995,89.436 34.931,123.07 14.479,-35.739 17.123,-85.377 29.939,-123.07 11.644,0 23.287,0 34.93,0 -12.062,58.113 -35.35,103.042 -44.909,164.093 -14.971,0 -29.94,0 -44.91,0 -9.471,-49.436 -28.165,-88.039 -39.92,-134.791 -23.287,0 -46.573,0 -69.86,0 -0.001,-9.767 -0.001,-19.535 -0.001,-29.302 z"
id="path8374"
style="fill-rule:evenodd" />
<path
d="m 385.065,76.186 c 12.301,-0.773 24.042,-0.888 29.939,5.86 -2.146,26.978 -3.585,49.781 4.99,76.186 45.408,12.307 33.898,-42.234 34.93,-82.046 9.98,0 19.96,0 29.94,0 0,37.116 0,74.232 0,111.349 -22.437,6.814 -26.368,-8.103 -29.94,-23.442 -14.933,22.293 -52.771,32.37 -69.859,5.86 0,-31.255 0,-62.511 0,-93.767 z"
id="path8376"
style="fill-rule:evenodd" />
<path
d="m 165.505,87.907 c 9.98,0 19.96,0 29.94,0 -2.328,48.057 6.913,109.699 -9.98,140.651 -23.857,2.642 -21.052,2.642 -44.91,0 24.686,-27.659 29.399,-78.774 24.95,-140.651 z"
id="path8378"
style="fill-rule:evenodd" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

758
imgsrc/random.svg Normal file
View File

@ -0,0 +1,758 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg10643"
sodipodi:version="0.32"
inkscape:version="0.46+devel"
sodipodi:docname="pointer.svgz"
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
inkscape:export-filename="/home/pinheiro/pics/oxygen-icons/scalable/actions/small/32x32/pointer.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
version="1.0">
<defs
id="defs10645">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective108" />
<linearGradient
id="linearGradient3233">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3235" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3237" />
</linearGradient>
<linearGradient
id="linearGradient3866">
<stop
id="stop3868"
offset="0"
style="stop-color:#fff299;stop-opacity:1;" />
<stop
id="stop3870"
offset="1"
style="stop-color:#dcd8bd;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient11059">
<stop
style="stop-color:#727272;stop-opacity:1;"
offset="0"
id="stop11061" />
<stop
id="stop11067"
offset="0.5"
style="stop-color:#a6a6a6;stop-opacity:1;" />
<stop
style="stop-color:#cdcdcd;stop-opacity:1;"
offset="0.75"
id="stop11069" />
<stop
style="stop-color:#acacac;stop-opacity:1;"
offset="1"
id="stop11063" />
</linearGradient>
<linearGradient
id="linearGradient10925">
<stop
style="stop-color:#bf0303;stop-opacity:0;"
offset="0"
id="stop10927" />
<stop
id="stop10978"
offset="0.39309064"
style="stop-color:#bf0303;stop-opacity:0;" />
<stop
id="stop10935"
offset="0.46538317"
style="stop-color:#bf0303;stop-opacity:0.49803922;" />
<stop
style="stop-color:#bf0303;stop-opacity:1;"
offset="0.5"
id="stop10976" />
<stop
id="stop10933"
offset="0.5"
style="stop-color:#bf0303;stop-opacity:1;" />
<stop
style="stop-color:#bf0303;stop-opacity:0.49803922;"
offset="0.55339807"
id="stop10937" />
<stop
id="stop10980"
offset="0.60542935"
style="stop-color:#bf0303;stop-opacity:0;" />
<stop
style="stop-color:#bf0303;stop-opacity:0;"
offset="1"
id="stop10929" />
</linearGradient>
<linearGradient
id="linearGradient10901">
<stop
id="stop10903"
offset="0"
style="stop-color:#fff299;stop-opacity:0;" />
<stop
style="stop-color:#fff299;stop-opacity:1;"
offset="0.5"
id="stop10909" />
<stop
id="stop10905"
offset="1"
style="stop-color:#fff299;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient10854">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop10856" />
<stop
id="stop10862"
offset="0.5"
style="stop-color:#000000;stop-opacity:0;" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop10858" />
</linearGradient>
<linearGradient
id="linearGradient10711">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop10713" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop10715" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient10875"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8967678,0.05935673,-0.05873468,0.8873664,-5.4012494,0.1392525)"
spreadMethod="reflect"
cx="18.708233"
cy="24.759357"
fx="18.708233"
fy="24.759357"
r="13.169441" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10925"
id="radialGradient10931"
cx="9.996233"
cy="23.364098"
fx="7.6629176"
fy="18.295921"
r="8.7188435"
gradientTransform="matrix(3.0577456,1.8802807,-0.9054531,1.4724637,3.4545267,-24.480143)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient10968"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8967678,0.05935673,-0.05873468,0.8873664,-5.4012494,0.1392525)"
spreadMethod="reflect"
cx="18.708233"
cy="24.759357"
fx="18.708233"
fy="24.759357"
r="13.169441" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10925"
id="radialGradient10971"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7002217,0.5715519,-0.4374946,2.0668853,-4.8632848,-26.818351)"
cx="9.1802711"
cy="24.942194"
fx="6.0336409"
fy="17.669048"
r="8.7188435" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10999">
<path
sodipodi:nodetypes="ccccc"
id="path11001"
d="M 3.6413483,1.9681703 3.779696,17.490509 14.887308,19.785771 21.079035,17.498126 3.6413483,1.9681703 z"
style="fill:#ff80ff;fill-opacity:1;fill-rule:evenodd;stroke:none" />
</clipPath>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10925"
id="radialGradient11003"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7002217,0.5715519,-0.4374946,2.0668853,-4.8632848,-26.818351)"
cx="8.2921495"
cy="23.935163"
fx="8.2488832"
fy="19.781427"
r="8.7188435" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10925"
id="radialGradient11030"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7002217,0.5715519,-0.4374946,2.0668853,-4.8632848,-26.818351)"
cx="8.2921495"
cy="23.935163"
fx="8.2488832"
fy="19.781427"
r="8.7188435" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient11032"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8967678,0.05935673,-0.05873468,0.8873664,-5.4012494,0.1392525)"
spreadMethod="reflect"
cx="18.708233"
cy="24.759357"
fx="18.708233"
fy="24.759357"
r="13.169441" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10925"
id="radialGradient11034"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7002217,0.5715519,-0.4374946,2.0668853,-4.8632848,-26.818351)"
cx="8.2921495"
cy="23.935163"
fx="8.2488832"
fy="19.781427"
r="8.7188435" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3294"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7030075,0.6357809,-0.8060735,0.8913044,14.84311,-8.1934483)"
spreadMethod="reflect"
cx="16.993044"
cy="20.648924"
fx="16.993044"
fy="20.648924"
r="13.169441" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient3297"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9823337,0,0,0.9823337,0.03300739,0.6182451)"
spreadMethod="pad"
x1="19.879225"
y1="12.061514"
x2="16.034332"
y2="15.552854" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient3353"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9823337,0,0,0.9823337,0.03300739,0.6182451)"
spreadMethod="pad"
x1="19.879225"
y1="12.061514"
x2="16.034332"
y2="15.552854" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3355"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7030075,0.6357809,-0.8060735,0.8913044,14.84311,-8.1934483)"
spreadMethod="reflect"
cx="16.993044"
cy="20.648924"
fx="16.993044"
fy="20.648924"
r="13.169441" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient3362"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9823337,0,0,0.9823337,0.03300739,0.6182451)"
spreadMethod="pad"
x1="19.879225"
y1="12.061514"
x2="16.034332"
y2="15.552854" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3364"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8341244,0.2489558,-0.2435026,0.8158514,0.7851109,-0.01382395)"
spreadMethod="reflect"
cx="17.54755"
cy="21.708042"
fx="17.54755"
fy="21.708042"
r="13.169441" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3367"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8151394,0.2358626,-0.2306962,0.7972824,0.7011221,-1.0582457)"
spreadMethod="reflect"
cx="17.54755"
cy="21.708042"
fx="17.54755"
fy="21.708042"
r="13.169441" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient3370"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9575785,-0.00803118,0.00803118,0.9575785,-0.0268605,-0.4359562)"
spreadMethod="pad"
x1="19.879225"
y1="12.061514"
x2="16.034332"
y2="15.552854" />
<linearGradient
y2="19.626715"
x2="10.711697"
y1="18.63658"
x1="9.7192469"
gradientTransform="matrix(3.547255,-0.03993894,0.03993894,3.547255,-27.397339,-48.790495)"
gradientUnits="userSpaceOnUse"
id="linearGradient3488"
xlink:href="#linearGradient10711"
inkscape:collect="always" />
<radialGradient
r="1.15625"
fy="20.478674"
fx="11.413477"
cy="20.478674"
cx="11.413477"
spreadMethod="pad"
gradientTransform="matrix(1.7083003,-0.01851949,0.01798426,1.6589328,-8.4797796,-13.189665)"
gradientUnits="userSpaceOnUse"
id="radialGradient3486"
xlink:href="#linearGradient3330"
inkscape:collect="always" />
<linearGradient
y2="19.626715"
x2="10.711697"
y1="18.63658"
x1="9.7192469"
gradientTransform="matrix(3.5474799,0,0,3.5474799,-26.927898,-62.356391)"
gradientUnits="userSpaceOnUse"
id="linearGradient3475"
xlink:href="#linearGradient10711"
inkscape:collect="always" />
<radialGradient
r="1.15625"
fy="20.478674"
fx="11.413477"
cy="20.478674"
cx="11.413477"
spreadMethod="pad"
gradientTransform="matrix(1.7083003,-0.01851949,0.01798426,1.6589328,-8.4797796,-13.189665)"
gradientUnits="userSpaceOnUse"
id="radialGradient3473"
xlink:href="#linearGradient3330"
inkscape:collect="always" />
<radialGradient
spreadMethod="reflect"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7809876,0.01449707,-0.0055455,0.2987498,-0.2924169,2.0957246)"
r="11.765625"
fy="10.911069"
fx="1.1416299"
cy="10.911069"
cx="1.1416299"
id="radialGradient3317"
xlink:href="#linearGradient3206"
inkscape:collect="always" />
<linearGradient
y2="26.641653"
x2="16.836901"
y1="6.8943019"
x1="5.6869311"
gradientTransform="translate(0,-7.2094174)"
gradientUnits="userSpaceOnUse"
id="linearGradient3265"
xlink:href="#linearGradient3267"
inkscape:collect="always" />
<linearGradient
y2="17.133453"
x2="16.836901"
y1="-2.6138983"
x1="5.6869311"
gradientTransform="translate(0,2.298783)"
gradientUnits="userSpaceOnUse"
id="linearGradient3261"
xlink:href="#linearGradient3267"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(0,-4.8361309)"
y2="24.268368"
x2="16.836901"
y1="4.5210156"
x1="5.6869311"
gradientUnits="userSpaceOnUse"
id="linearGradient3257"
xlink:href="#linearGradient3267"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(0,-2.4628444)"
y2="21.895081"
x2="16.836901"
y1="2.1477292"
x1="5.6869311"
gradientUnits="userSpaceOnUse"
id="linearGradient3249"
xlink:href="#linearGradient3267"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="19.432236"
x2="16.836901"
y1="-0.31511527"
x1="5.6869311"
id="linearGradient3239"
xlink:href="#linearGradient3267"
inkscape:collect="always" />
<linearGradient
y2="19.626715"
x2="10.711697"
y1="18.384007"
x1="9.8687286"
gradientTransform="matrix(3.6334443,0,0,3.6334443,-27.580699,-51.677773)"
gradientUnits="userSpaceOnUse"
id="linearGradient3220"
xlink:href="#linearGradient10711"
inkscape:collect="always" />
<radialGradient
r="1.15625"
fy="20.478674"
fx="11.413477"
cy="20.478674"
cx="11.413477"
spreadMethod="pad"
gradientTransform="matrix(1.7083003,-0.01851949,0.01798426,1.6589328,-8.4797796,-13.189665)"
gradientUnits="userSpaceOnUse"
id="radialGradient3218"
xlink:href="#linearGradient10711"
inkscape:collect="always" />
<linearGradient
id="linearGradient2657">
<stop
id="stop2659"
offset="0"
style="stop-color:#ff80ff;stop-opacity:1;" />
<stop
id="stop2661"
offset="1"
style="stop-color:#ff80ff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3206">
<stop
style="stop-color:#b1d28f;stop-opacity:1;"
offset="0"
id="stop3208" />
<stop
style="stop-color:#b1d28f;stop-opacity:1;"
offset="1"
id="stop3210" />
</linearGradient>
<linearGradient
id="linearGradient3241">
<stop
id="stop3243"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop3245"
offset="1"
style="stop-color:#debc85;stop-opacity:0" />
</linearGradient>
<linearGradient
id="linearGradient3267">
<stop
style="stop-color:#debc85;stop-opacity:1;"
offset="0"
id="stop3269" />
<stop
style="stop-color:#debc85;stop-opacity:0;"
offset="1"
id="stop3271" />
</linearGradient>
<linearGradient
id="linearGradient3273">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3275" />
<stop
style="stop-color:#debc85;stop-opacity:0"
offset="1"
id="stop3277" />
</linearGradient>
<linearGradient
id="linearGradient3279">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3281" />
<stop
style="stop-color:#debc85;stop-opacity:0"
offset="1"
id="stop3283" />
</linearGradient>
<linearGradient
id="linearGradient3285">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3287" />
<stop
style="stop-color:#debc85;stop-opacity:0"
offset="1"
id="stop3289" />
</linearGradient>
<linearGradient
id="linearGradient3330">
<stop
style="stop-color:#ff80ff;stop-opacity:0;"
offset="0"
id="stop3332" />
<stop
style="stop-color:#666666;stop-opacity:1;"
offset="1"
id="stop3334" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient4021"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9318803,-0.2210697,0.2308678,0.9731826,-3.9252239,2.7241703)"
spreadMethod="pad"
cx="11.074039"
cy="20.428291"
fx="11.074039"
fy="20.428291"
r="1.15625" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient4023"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8514941,0.5243642,-0.5243642,0.8514941,24.154135,2.8247022)"
x1="21.461079"
y1="23.349636"
x2="22.96941"
y2="28.038134" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="linearGradient4030"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8514941,0.5243642,-0.5243642,0.8514941,18.007546,-15.657615)"
x1="21.461079"
y1="23.349636"
x2="22.96941"
y2="28.038134" />
<filter
inkscape:collect="always"
x="-0.20028582"
width="1.4005716"
y="-0.11837127"
height="1.2367425"
id="filter3484">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.97202214"
id="feGaussianBlur3486" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3490"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1086176,-0.4093269,0.6608062,1.7897223,-9.2289678,-4.0397151)"
spreadMethod="reflect"
cx="8.8133469"
cy="14.235861"
fx="8.8133469"
fy="14.235861"
r="5.3238101" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3496">
<rect
style="opacity:0.62633481;fill:none;stroke:#000000;stroke-width:0.19602102;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect3498"
width="13.277639"
height="22.63365"
x="5.309958"
y="1.2316679"
ry="1.171887" />
</clipPath>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3508"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1106976,-0.4036489,0.6516398,1.7930801,-9.2127514,-4.7972628)"
spreadMethod="reflect"
cx="8.8133469"
cy="14.235861"
fx="8.8133469"
fy="14.235861"
r="5.3238101" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3233"
id="linearGradient3240"
x1="9.4485903"
y1="2.761672"
x2="7.6776314"
y2="19.013866"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.1741381,0,0,4.1613891,14.977639,14.527008)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient10711"
id="radialGradient3253"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.7157796,-1.4428762,2.6160831,8.4980426,-22.699134,-22.277012)"
spreadMethod="reflect"
cx="8.2230186"
cy="14.316785"
fx="8.2230186"
fy="14.316785"
r="5.3238101" />
<filter
inkscape:collect="always"
id="filter3757"
x="-0.14567212"
width="1.2913442"
y="-0.098205952"
height="1.1964119">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.79012916"
id="feGaussianBlur3759" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2"
inkscape:cx="8.5584572"
inkscape:cy="52.628863"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1280"
inkscape:window-height="742"
inkscape:window-x="296"
inkscape:window-y="56"
showguides="true"
inkscape:guide-bbox="true"
width="24px"
height="24px"
inkscape:object-paths="false"
inkscape:object-nodes="true"
inkscape:snap-nodes="false"
inkscape:snap-global="false">
<inkscape:grid
type="xygrid"
id="grid3664"
empspacing="2"
visible="true"
enabled="true"
spacingx="2.6666px"
spacingy="2.6666px" />
<sodipodi:guide
orientation="1,0"
position="10.507812,7.328125"
id="guide3666" />
<sodipodi:guide
orientation="0,1"
position="10.292968,7.5546875"
id="guide3668" />
</sodipodi:namedview>
<metadata
id="metadata10648">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:0.18099551;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3757);enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
d="m 5,19 c 5.24e-5,0.523584 0.4764155,0.999948 1,1 l 3.59375,0 2.5,2.5625 c 0.272702,0.267764 0.706204,0.357015 1.0625,0.21875 l 1.25,-0.46875 c 0.353635,-0.127466 0.619754,-0.46962 0.65625,-0.84375 l 0.34375,-3.3125 2.40625,-3 c 0.296435,-0.374818 0.26821,-0.967546 -0.0625,-1.3125 L 5.2034921,1.0488435 5,19 z"
id="path3670"
sodipodi:nodetypes="cccccccccccc"
transform="matrix(3.8351065,0,0,3.8305733,20.000787,24.35592)" />
<path
style="fill:#201020;fill-rule:evenodd;stroke:#595959;stroke-width:5.33333349;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 40.022468,18.688398 0,66.582224 16.696553,0 L 68.436432,97.122145 73.70512,95.153883 75.247683,80.05609 86.03678,66.636329 40.022468,18.688398 z"
id="path3502"
sodipodi:nodetypes="cccccccc" />
<path
sodipodi:nodetypes="cccccccc"
id="path3504"
d="m 40.022468,18.688397 0,66.58222 16.696554,0 11.717412,11.851511 5.268688,-1.968253 1.54256,-15.09779 L 85.892267,66.67168 40.022468,18.688397 z"
style="fill:#c4c4c4;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<path
style="fill:url(#radialGradient3253);fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 40.022468,18.688397 0,66.58222 16.696554,0 11.717412,11.851511 5.268688,-1.968253 1.54256,-15.09779 10.56036,-13.22296 -45.785574,-48.144728 z"
id="path3506"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:none;stroke:url(#linearGradient3240);stroke-width:2.667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 41.38037,83.915714 58.396539,84.11082 69.204513,94.929961 72.193882,93.656427 73.839587,79.399575 84.08764,66.742537 41.448646,22.246495 41.38037,83.915714 z"
id="path2253"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,23 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1312361378(BasicNewsRecipe):
title = u'Carta capital'
__author__ = 'Pablo Aldama'
language = 'pt_BR'
oldest_article = 9
max_articles_per_feed = 100
feeds = [(u'Politica', u'http://www.cartacapital.com.br/category/politica/feed')
,(u'Economia', u'http://www.cartacapital.com.br/category/economia/feed')
,(u'Cultura', u'http://www.cartacapital.com.br/category/cultura/feed')
,(u'Internacional', u'http://www.cartacapital.com.br/category/internacional/feed')
,(u'Saude', u'http://www.cartacapital.com.br/category/saude/feed')
,(u'Sociedade', u'http://www.cartacapital.com.br/category/sociedade/feed')
,(u'Tecnologia', u'http://www.cartacapital.com.br/category/tecnologia/feed')
,(u'Carta na escola', u'http://www.cartacapital.com.br/category/carta-na-escola/feed')
,(u'Carta fundamental', u'http://www.cartacapital.com.br/category/carta-fundamental/feed')
,(u'Carta verde', u'http://www.cartacapital.com.br/category/carta-verde/feed')
]
def print_version(self, url):
return url + '/print'

View File

@ -30,8 +30,14 @@ class CnetNews(BasicNewsRecipe):
remove_tags = [
dict(name='div', attrs={'id':'tweetmemeAndFacebook'})
,dict(name='ul', attrs={'class':'contentTools'})
,dict(name='aside', attrs={'id':'filed'})
,dict(name='div', attrs={'class':'postLinks'})
,dict(name='span', attrs={'class':'shareButton'})
,dict(name='span', attrs={'class':'printButton'})
,dict(name='span', attrs={'class':'emailButton'})
,dict(name='div', attrs={'class':'editorBio'})
]
keep_only_tags = dict(name='div', attrs={'class':'txtWrap'})
keep_only_tags = dict(name='div', attrs={'class':'post'})
feeds = [(u'News', u'http://news.cnet.com/2547-1_3-0-20.xml')]

View File

@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
from collections import OrderedDict
import time, re
import re
class Economist(BasicNewsRecipe):
@ -31,45 +31,41 @@ class Economist(BasicNewsRecipe):
{'class': lambda x: x and 'share-links-header' in x},
]
keep_only_tags = [dict(id='ec-article-body')]
needs_subscription = False
no_stylesheets = True
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
lambda x:'</html>')]
# economist.com has started throttling after about 60% of the total has
# downloaded with connection reset by peer (104) errors.
delay = 1
needs_subscription = False
'''
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.economist.com')
req = mechanize.Request(
'http://www.economist.com/members/members.cfm?act=exec_login',
headers = {
'Referer':'http://www.economist.com/',
},
data=urllib.urlencode({
'logging_in' : 'Y',
'returnURL' : '/',
'email_address': self.username,
'fakepword' : 'Password',
'pword' : self.password,
'x' : '0',
'y' : '0',
}))
br.open(req).read()
if self.username and self.password:
br.open('http://www.economist.com/user/login')
br.select_form(nr=1)
br['name'] = self.username
br['pass'] = self.password
res = br.submit()
raw = res.read()
if '>Log out<' not in raw:
raise ValueError('Failed to login to economist.com. '
'Check your username and password.')
return br
'''
def parse_index(self):
try:
return self.economist_parse_index()
except:
raise
self.log.warn(
'Initial attempt to parse index failed, retrying in 30 seconds')
time.sleep(30)
return self.economist_parse_index()
def economist_parse_index(self):
soup = self.index_to_soup(self.INDEX)
div = soup.find('div', attrs={'class':'issue-image'})
if div is not None:
img = div.find('img', src=True)
if img is not None:
self.cover_url = img['src']
feeds = OrderedDict()
for section in soup.findAll(attrs={'class':lambda x: x and 'section' in
x}):
@ -109,7 +105,9 @@ class Economist(BasicNewsRecipe):
'description':'', 'date':''})
if articles:
feeds[section_title] = articles
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
if not ans:

View File

@ -1,3 +1,140 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
economist.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
from collections import OrderedDict
import time, re
class Economist(BasicNewsRecipe):
title = 'The Economist'
language = 'en'
__author__ = "Kovid Goyal"
INDEX = 'http://www.economist.com/printedition'
description = ('Global news and current affairs from a European'
' perspective. Best downloaded on Friday mornings (GMT)')
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
oldest_article = 7.0
cover_url = 'http://media.economist.com/sites/default/files/imagecache/print-cover-thumbnail/print-covers/currentcoverus_large.jpg'
#cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
remove_tags = [
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk', 'ec-article-info',
'share_inline_header', 'related-items']}),
{'class': lambda x: x and 'share-links-header' in x},
]
keep_only_tags = [dict(id='ec-article-body')]
needs_subscription = False
no_stylesheets = True
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
lambda x:'</html>')]
# economist.com has started throttling after about 60% of the total has
# downloaded with connection reset by peer (104) errors.
delay = 1
def parse_index(self):
try:
return self.economist_parse_index()
except:
raise
self.log.warn(
'Initial attempt to parse index failed, retrying in 30 seconds')
time.sleep(30)
return self.economist_parse_index()
def economist_parse_index(self):
soup = self.index_to_soup(self.INDEX)
div = soup.find('div', attrs={'class':'issue-image'})
if div is not None:
img = div.find('img', src=True)
if img is not None:
self.cover_url = img['src']
feeds = OrderedDict()
for section in soup.findAll(attrs={'class':lambda x: x and 'section' in
x}):
h4 = section.find('h4')
if h4 is None:
continue
section_title = self.tag_to_string(h4).strip()
if not section_title:
continue
self.log('Found section: %s'%section_title)
articles = []
for h5 in section.findAll('h5'):
article_title = self.tag_to_string(h5).strip()
if not article_title:
continue
data = h5.findNextSibling(attrs={'class':'article'})
if data is None: continue
a = data.find('a', href=True)
if a is None: continue
url = a['href']
if url.startswith('/'): url = 'http://www.economist.com'+url
url += '/print'
article_title += ': %s'%self.tag_to_string(a).strip()
articles.append({'title':article_title, 'url':url,
'description':'', 'date':''})
if not articles:
# We have last or first section
for art in section.findAll(attrs={'class':'article'}):
a = art.find('a', href=True)
if a is not None:
url = a['href']
if url.startswith('/'): url = 'http://www.economist.com'+url
url += '/print'
title = self.tag_to_string(a)
if title:
articles.append({'title':title, 'url':url,
'description':'', 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
if not ans:
raise Exception('Could not find any articles, either the '
'economist.com server is having trouble and you should '
'try later or the website format has changed and the '
'recipe needs to be updated.')
return ans
def eco_find_image_tables(self, soup):
for x in soup.findAll('table', align=['right', 'center']):
if len(x.findAll('font')) in (1,2) and len(x.findAll('img')) == 1:
yield x
def postprocess_html(self, soup, first):
body = soup.find('body')
for name, val in body.attrs:
del body[name]
for table in list(self.eco_find_image_tables(soup)):
caption = table.find('font')
img = table.find('img')
div = Tag(soup, 'div')
div['style'] = 'text-align:left;font-size:70%'
ns = NavigableString(self.tag_to_string(caption))
div.insert(0, ns)
div.insert(1, Tag(soup, 'br'))
del img['width']
del img['height']
img.extract()
div.insert(2, img)
table.replaceWith(div)
return soup
'''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.utils.threadpool import ThreadPool, makeRequests
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
@ -145,3 +282,5 @@ class Economist(BasicNewsRecipe):
div.insert(2, img)
table.replaceWith(div)
return soup
'''

View File

@ -0,0 +1,40 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1313609361(BasicNewsRecipe):
news = True
title = u'El Mostrador'
__author__ = 'Alex Mitrani'
description = u'Chilean online newspaper'
publisher = u'La Plaza S.A.'
category = 'news, rss'
oldest_article = 7
max_articles_per_feed = 100
summary_length = 1000
language = 'es_CL'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
masthead_url = 'http://www.elmostrador.cl/assets/img/logo-elmostrador-m.jpg'
remove_tags_before = dict(name='div', attrs={'class':'news-heading cf'})
remove_tags_after = dict(name='div', attrs={'class':'footer-actions cf'})
remove_tags = [dict(name='div', attrs={'class':'footer-actions cb cf'})
,dict(name='div', attrs={'class':'news-aside fl'})
,dict(name='div', attrs={'class':'footer-actions cf'})
,dict(name='div', attrs={'class':'user-bar','id':'top'})
,dict(name='div', attrs={'class':'indicators'})
,dict(name='div', attrs={'id':'header'})
]
feeds = [(u'Temas Destacados'
, u'http://www.elmostrador.cl/destacado/feed/')
, (u'El D\xeda', u'http://www.elmostrador.cl/dia/feed/')
, (u'Pa\xeds', u'http://www.elmostrador.cl/noticias/pais/feed/')
, (u'Mundo', u'http://www.elmostrador.cl/noticias/mundo/feed/')
, (u'Negocios', u'http://www.elmostrador.cl/noticias/negocios/feed/')
, (u'Cultura', u'http://www.elmostrador.cl/noticias/cultura/feed/')
, (u'Vida en L\xednea', u'http://www.elmostrador.cl/vida-en-linea/feed/')
, (u'Opini\xf3n & Blogs', u'http://www.elmostrador.cl/opinion/feed/')
]

View File

@ -18,7 +18,7 @@ class ElMundo(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'iso8859_15'
language = 'es_ES'
language = 'es'
masthead_url = 'http://estaticos03.elmundo.es/elmundo/iconos/v4.x/v4.01/bg_h1.png'
publication_type = 'newspaper'
extra_css = """

View File

@ -0,0 +1,28 @@
from calibre.web.feeds.news import BasicNewsRecipe
class Escrevinhador(BasicNewsRecipe):
title = 'Blog Escrevinhador'
__author__ = 'Diniz Bortolotto'
description = 'Posts do Blog Escrevinhador'
publisher = 'Rodrigo Viana'
oldest_article = 5
max_articles_per_feed = 20
category = 'news, politics, Brazil'
language = 'pt_BR'
publication_type = 'news and politics portal'
use_embedded_content = False
no_stylesheets = True
remove_javascript = True
feeds = [(u'Blog Escrevinhador', u'http://www.rodrigovianna.com.br/feed')]
reverse_article_order = True
remove_tags_after = [dict(name='div', attrs={'class':'text'})]
remove_tags = [
dict(id='header'),
dict(name='p', attrs={'class':'tags'}),
dict(name='div', attrs={'class':'sociable'})
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

@ -1,70 +1,86 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
www.independent.co.uk
'''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class TheIndependent(BasicNewsRecipe):
title = u'The Independent'
language = 'en_GB'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 30
encoding = 'latin1'
title = 'The Independent'
__author__ = 'Darko Miletic'
description = 'Independent News - Breaking news, comment and features from The Independent newspaper'
publisher = 'The Independent'
category = 'news, politics, UK'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':'related-articles'}),
dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
dict(name='ul', attrs={'class':'article-tools'}),
dict(name='ul', attrs={'class':'articleTools'}),
encoding = 'cp1252'
use_embedded_content = False
language = 'en_GB'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://www.independent.co.uk/independent.co.uk/images/logo-london.png'
extra_css = """
h1{font-family: Georgia,serif }
body{font-family: Verdana,Arial,Helvetica,sans-serif}
img{margin-bottom: 0.4em; display:block}
.info,.caption,.credits{font-size: x-small}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags =[
dict(name=['meta','link','object','embed','iframe','base','style'])
,dict(attrs={'class':['related-articles','share','googleCols','article-tools','paging','googleArt']})
,dict(attrs={'id':['newsVideoPlayer','yahoobook','google-intext']})
]
keep_only_tags =[dict(attrs={'id':'article'})]
remove_attributes=['lang','onclick','width','xmlns:fb']
feeds = [
('UK',
'http://www.independent.co.uk/news/uk/rss'),
('World',
'http://www.independent.co.uk/news/world/rss'),
('Business',
'http://www.independent.co.uk/news/business/rss'),
('People',
'http://www.independent.co.uk/news/people/rss'),
('Science',
'http://www.independent.co.uk/news/science/rss'),
('Media',
'http://www.independent.co.uk/news/media/rss'),
('Education',
'http://www.independent.co.uk/news/education/rss'),
('Obituaries',
'http://www.independent.co.uk/news/obituaries/rss'),
('Opinion',
'http://www.independent.co.uk/opinion/rss'),
('Environment',
'http://www.independent.co.uk/environment/rss'),
('Sport',
'http://www.independent.co.uk/sport/rss'),
('Life and Style',
'http://www.independent.co.uk/life-style/rss'),
('Arts and Entertainment',
'http://www.independent.co.uk/arts-entertainment/rss'),
('Travel',
'http://www.independent.co.uk/travel/rss'),
('Money',
'http://www.independent.co.uk/money/rss'),
(u'UK' , u'http://www.independent.co.uk/news/uk/rss' )
,(u'World' , u'http://www.independent.co.uk/news/world/rss' )
,(u'Business' , u'http://www.independent.co.uk/news/business/rss' )
,(u'People' , u'http://www.independent.co.uk/news/people/rss' )
,(u'Science' , u'http://www.independent.co.uk/news/science/rss' )
,(u'Media' , u'http://www.independent.co.uk/news/media/rss' )
,(u'Education' , u'http://www.independent.co.uk/news/education/rss' )
,(u'Leading Articles' , u'http://www.independent.co.uk/opinion/leading-articles/rss')
,(u'Comentators' , u'http://www.independent.co.uk/opinion/commentators/rss' )
,(u'Columnists' , u'http://www.independent.co.uk/opinion/columnists/rss' )
,(u'Letters' , u'http://www.independent.co.uk/opinion/letters/rss' )
,(u'Big Question' , u'http://www.independent.co.uk/extras/big-question/rss' )
,(u'Sport' , u'http://www.independent.co.uk/sport/rss' )
,(u'Life&Style' , u'http://www.independent.co.uk/life-style/rss' )
,(u'Arts&Entertainment' , u'http://www.independent.co.uk/arts-entertainment/rss' )
,(u'Travel' , u'http://www.independent.co.uk/travel/rss' )
,(u'Money' , u'http://www.independent.co.uk/money/rss' )
]
def get_article_url(self, article):
return article.get('guid', None)
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'mainColumn'})
#td = heading.findParent(name='td')
#td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
return soup
for item in soup.body.findAll(style=True):
del item['style']
for item in soup.body.findAll(['author','preform']):
item.name='span'
for item in soup.body.findAll('img'):
if not item.has_key('alt'):
item['alt'] = 'image'
for item in soup.body.findAll('div', attrs={'class':['clear-o','body','photoCaption']}):
item.name = 'p'
for item in soup.body.findAll('div'):
if not item.attrs and not item.contents:
item.extract()
soup2 = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
soup2.body.replaceWith(soup.body)
return soup2

View File

@ -1,55 +1,46 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2011, Oscar Megia Lopez'
'''
juventudrebelde.cu
'''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Juventudrebelde(BasicNewsRecipe):
title = 'Juventud Rebelde'
__author__ = 'Darko Miletic'
description = 'Diario de la Juventud Cubana'
publisher = 'Juventud rebelde'
category = 'news, politics, Cuba'
oldest_article = 2
class JuventudRebelde(BasicNewsRecipe):
title = u'Juventud Rebelde'
__author__ = 'Oscar Megia Lopez'
description = 'Periodico cubano'
oldest_article = 30
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
encoding = 'cp1252'
language = 'es_CU'
encoding = 'utf8'
publisher = 'Juventud Rebelde'
category = 'Noticias'
language = 'es'
publication_type = 'Periodico'
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif } .title{font-weight: bold} .read{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} '
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
,'linearize_tables': True
}
cover_url = strftime('http://www.juventudrebelde.cu/UserFiles/File/impreso/iportada-%Y-%m-%d.jpg')
remove_javascript = True
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher', publisher
, '--ignore-tables'
keep_only_tags = [
dict(name='div', attrs={'class':['title']})
,dict(attrs={'class':['read']})
,dict(attrs={'class':['author']})
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
keep_only_tags = [dict(name='div', attrs={'id':'noticia'})]
feeds = [
(u'Generales', u'http://www.juventudrebelde.cu/rss/generales.php' )
,(u'Cuba', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=cuba' )
,(u'Internacionales', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=internacionales' )
,(u'Opinion', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=opinion' )
,(u'Cultura', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=cultura' )
,(u'Deportes', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=deportes' )
,(u'Lectura', u'http://www.juventudrebelde.cu/rss/generales.php?seccion=lectura' )
remove_tags = [
dict(name='div', attrs={'class':['share']}),
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="es-CU"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
return soup
remove_attributes = ['width','height']
feeds = [(u'Generales', u'http://www.juventudrebelde.cu/get/rss/grupo/generales/'), (u'Internacionales', u'http://www.psychologytoday.com/blog/romance-redux/feed'), (u'Ciencia y Tecnica', u'http://www.juventudrebelde.cu/get/rss/noticias/ciencia-tecnica/'), (u'Opini\xf3n', u'http://www.juventudrebelde.cu/get/rss/noticias/opinion/'), (u'Cuba', u'http://www.juventudrebelde.cu/get/rss/noticias/cuba/'), (u'Cultura', u'http://www.juventudrebelde.cu/get/rss/noticias/cultura/'), (u'Deportes', u'http://www.juventudrebelde.cu/get/rss/noticias/deportes')]

View File

@ -26,7 +26,7 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
use_embedded_content = False
encoding = 'utf-8'
language = 'es_ES'
language = 'es'
timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [

View File

@ -17,18 +17,15 @@ class Lanacion(BasicNewsRecipe):
use_embedded_content = False
no_stylesheets = True
language = 'es_AR'
delay = 14
publication_type = 'newspaper'
remove_empty_feeds = True
masthead_url = 'http://www.lanacion.com.ar/_ui/desktop/imgs/layout/logos/ln341x47.gif'
masthead_url = 'http://www.lanacion.com.ar/_ui/desktop/imgs/layout/logos/ln-home.gif'
extra_css = """
h1{font-family: Georgia,serif}
h2{color: #626262; font-weight: normal; font-size: 1.1em}
h1{font-family: TheSans,Arial,sans-serif}
body{font-family: Arial,sans-serif}
img{margin-top: 0.5em; margin-bottom: 0.2em; display: block}
.notaFecha{color: #808080; font-size: small}
.notaEpigrafe{font-size: x-small}
.topNota h1{font-family: Arial,sans-serif}
img{display: block}
.firma,.fecha{font-size: small}
.epigrafe-columna{font-size: x-small}
"""
@ -39,21 +36,13 @@ class Lanacion(BasicNewsRecipe):
, 'language' : language
}
keep_only_tags = [
dict(name='div', attrs={'class':['topNota','itemHeader','nota','itemBody']})
,dict(name='div', attrs={'id':'content'})
]
remove_tags = [
dict(name='div' , attrs={'class':'notaComentario floatFix noprint' })
,dict(name='ul' , attrs={'class':['cajaHerramientas cajaTop noprint','herramientas noprint']})
,dict(name='div' , attrs={'class':['titulosMultimedia','herramientas noprint','cajaHerramientas noprint','cajaHerramientas floatFix'] })
,dict(attrs={'class':['izquierda','espacio17','espacio10','espacio20','floatFix ultimasNoticias','relacionadas','titulosMultimedia','derecha','techo color','encuesta','izquierda compartir','floatFix','videoCentro']})
,dict(name=['iframe','embed','object','form','base','hr','meta','link','input'])
dict(name=['iframe','embed','object','meta','link'])
,dict(attrs={'id':['herramientas','relacionadas','ampliar']})
]
remove_tags_after = dict(attrs={'class':['tags','nota-destacado']})
remove_attributes = ['height','width','visible','onclick','data-count','name']
remove_tags_before = dict(attrs={'id':'encabezado'})
remove_tags_after = dict(attrs={'id':'relacionadas'})
feeds = [
(u'Politica' , u'http://servicios.lanacion.com.ar/herramientas/rss/categoria_id=30' )
@ -92,6 +81,15 @@ class Lanacion(BasicNewsRecipe):
return None
return link
def get_cover_url(self):
soup = self.index_to_soup('http://www.lanacion.com.ar/edicion-impresa')
atap = soup.find(attrs={'class':'tapa'})
if atap:
li = atap.find('img')
if li:
return li['src']
return None
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']

View File

@ -23,7 +23,7 @@ class LaTribuna(BasicNewsRecipe):
encoding = 'utf-8'
language = 'es_HN'
lang = 'es-HN'
lang = 'es_HN'
direction = 'ltr'
html2lrf_options = [

View File

@ -19,7 +19,7 @@ class Marca(BasicNewsRecipe):
use_embedded_content = False
delay = 1
encoding = 'iso-8859-15'
language = 'es_ES'
language = 'es'
publication_type = 'newsportal'
masthead_url = 'http://estaticos.marca.com/deporte/img/v3.0/img_marca-com.png'
extra_css = """

View File

@ -2,6 +2,9 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro Nieuws NL'
description = u'Metro Nieuws - NL'
# Version 1.2, updated cover image to match the changed website.
# added info date on title
oldest_article = 2
max_articles_per_feed = 100
__author__ = u'DrMerry'
@ -10,11 +13,11 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
simultaneous_downloads = 5
delay = 1
# timefmt = ' [%A, %d %B, %Y]'
timefmt = ''
timefmt = ' [%A, %d %b %Y]'
no_stylesheets = True
remove_javascript = True
remove_empty_feeds = True
cover_url = 'http://www.readmetro.com/img/en/metroholland/last/1/small.jpg'
cover_url = 'http://www.oldreadmetro.com/img/en/metroholland/last/1/small.jpg'
remove_empty_feeds = True
publication_type = 'newspaper'
remove_tags_before = dict(name='div', attrs={'id':'date'})

View File

@ -8,6 +8,9 @@ class Newsweek(BasicNewsRecipe):
language = 'en'
encoding = 'utf-8'
no_stylesheets = True
recipe_disabled = ('Newsweek was taken over by The Daily Beast,'
' newsweek.com no longer exists, so this recipe '
' has been disabled.')
BASE_URL = 'http://www.newsweek.com'

View File

@ -1,91 +1,135 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, matek09, matek09@gmail.com'
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ptempfile import PersistentTemporaryFile
import datetime
class Newsweek(BasicNewsRecipe):
FIND_LAST_FULL_ISSUE = True
EDITION = '0'
EXCLUDE_LOCKED = True
LOCKED_ICO = 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif'
DATE = None
YEAR = datetime.datetime.now().year
title = u'Newsweek Polska'
__author__ = 'matek09'
description = 'Weekly magazine'
encoding = 'utf-8'
no_stylesheets = True
language = 'pl'
remove_javascript = True
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'article'}))
temp_files = []
articles_are_obfuscated = True
remove_tags =[]
remove_tags.append(dict(name = 'div', attrs = {'class' : 'copy'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'url'}))
extra_css = '''
.body {font-size: small}
.author {font-size: x-small}
.lead {font-size: x-small}
.title{font-size: x-large; font-weight: bold}
'''
def get_obfuscated_article(self, url):
br = self.get_browser()
br.open(url)
source = br.response().read()
page = self.index_to_soup(source)
def print_version(self, url):
return url.replace("http://www.newsweek.pl/artykuly/wydanie/" + str(self.EDITION), "http://www.newsweek.pl/artykuly") + '/print'
main_section = page.find(id='mainSection')
def is_locked(self, a):
if a.findNext('img')['src'] == 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif':
return True
else:
return False
title = main_section.find('h1')
info = main_section.find('ul', attrs={'class' : 'articleInfo'})
authors = info.find('li').find('h4')
article = main_section.find('div', attrs={'id' : 'article'})
html = unicode(title) + unicode(authors) + unicode(article)
next = main_section.find('li', attrs={'class' : 'next'})
while next:
url = next.find('a')['href']
br.open(url)
source = br.response().read()
page = self.index_to_soup(source)
main_section = page.find(id='mainSection')
article = main_section.find('div', attrs={'id' : 'article'})
aside = article.find(id='articleAside')
if aside is not None:
aside.extract()
html = html + unicode(article)
next = main_section.find('li', attrs={'class' : 'next'})
self.temp_files.append(PersistentTemporaryFile('_temparse.html'))
self.temp_files[-1].write(html)
self.temp_files[-1].close()
return self.temp_files[-1].name
def is_full(self, issue_soup):
if len(issue_soup.findAll('img', attrs={'src' : 'http://www.newsweek.pl/bins/media/static/newsweek/img/ico_locked.gif'})) > 1:
return False
else:
return True
def find_last_full_issue(self):
frame_url = 'http://www.newsweek.pl/Frames/IssueCover.aspx'
while True:
frame_soup = self.index_to_soup(frame_url)
self.EDITION = frame_soup.find('a', attrs={'target' : '_parent'})['href'].replace('/wydania/','')
main_section = issue_soup.find(id='mainSection')
next = main_section.find('li', attrs={'class' : 'next'})
if len(main_section.findAll(attrs={'class' : 'locked'})) > 1:
return False
elif next is None:
return True
else:
issue_soup = self.index_to_soup(next.find('a')['href'])
def find_last_full_issue(self, archive_url):
archive_soup = self.index_to_soup(archive_url)
select = archive_soup.find('select', attrs={'id' : 'paper_issue_select'})
for option in select.findAll(lambda tag: tag.name == 'option' and tag.has_key('value')):
self.EDITION = option['value'].replace('http://www.newsweek.pl/wydania/','')
issue_soup = self.index_to_soup('http://www.newsweek.pl/wydania/' + self.EDITION)
if self.is_full(issue_soup):
break
frame_url = 'http://www.newsweek.pl/Frames/' + frame_soup.find(lambda tag: tag.name == 'span' and not tag.attrs).a['href']
return
self.YEAR = self.YEAR - 1
self.find_last_full_issue(archive_url + ',' + str(self.YEAR))
def parse_index(self):
if self.FIND_LAST_FULL_ISSUE:
self.find_last_full_issue()
archive_url = 'http://www.newsweek.pl/wydania/archiwum'
self.find_last_full_issue(archive_url)
soup = self.index_to_soup('http://www.newsweek.pl/wydania/' + self.EDITION)
img = soup.find('img', id="ctl00_C1_PaperIsssueView_IssueImage", src=True)
self.DATE = self.tag_to_string(soup.find('span', attrs={'class' : 'data'}))
main_section = soup.find(id='mainSection')
img = main_section.find(lambda tag: tag.name == 'img' and tag.has_key('alt') and tag.has_key('title'))
self.cover_url = img['src']
feeds = []
parent = soup.find(id='content-left-big')
for txt in parent.findAll(attrs={'class':'txt_normal_red strong'}):
articles = list(self.find_articles(txt))
if len(articles) > 0:
section = self.tag_to_string(txt).capitalize()
feeds.append((section, articles))
articles = {}
sections = []
while True:
news_list = main_section.find('ul', attrs={'class' : 'newsList'})
for h2 in news_list.findAll('h2'):
article = self.create_article(h2)
category_div = h2.findNext('div', attrs={'class' : 'kategorie'})
section = self.tag_to_string(category_div)
if articles.has_key(section):
articles[section].append(article)
else:
articles[section] = [article]
sections.append(section)
next = main_section.find('li', attrs={'class' : 'next'})
if next is None:
break
soup = self.index_to_soup(next.find('a')['href'])
main_section = soup.find(id='mainSection')
for section in sections:
feeds.append((section, articles[section]))
return feeds
def find_articles(self, txt):
for a in txt.findAllNext( attrs={'class':['strong','hr']}):
if a.name in "div":
break
if (not self.FIND_LAST_FULL_ISSUE) & self.EXCLUDE_LOCKED & self.is_locked(a):
continue
yield {
'title' : self.tag_to_string(a),
'url' : 'http://www.newsweek.pl' + a['href'],
'date' : '',
'description' : ''
}
def create_article(self, h2):
article = {}
a = h2.find('a')
article['title'] = self.tag_to_string(a)
article['url'] = a['href']
article['date'] = self.DATE
desc = h2.findNext('p')
if desc is not None:
article['description'] = self.tag_to_string(desc)
else:
article['description'] = ''
return article

View File

@ -85,4 +85,5 @@ class NikkeiNet_paper_subscription(BasicNewsRecipe):
description='', content=''))
result.append([sect_title, sect_result])
#pp.pprint(result)
return result

35
recipes/novinite.recipe Normal file
View File

@ -0,0 +1,35 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1308572538(BasicNewsRecipe):
title = u'Novinite.com'
__author__ = 'Martin Tsanchev'
description = 'Real time provider of the latest Bulgarian news in English'
category = 'Business, Politics, Society, Sports, Crime, Lifestyle, World, People'
language = 'en_BG'
encoding = 'utf-8'
oldest_article = 7
max_articles_per_feed = 10
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
remove_tags = [dict(name='a', attrs={'class':'twitter-share-button'})]
remove_tags_after = dict(id='textsize')
no_stylesheets = True
feeds = [(u'Business', u'http://www.novinite.com/services/news_rdf.php?category_id=1'),
(u'Finance', u'http://www.novinite.com/services/news_rdf.php?category_id=15'),
(u'Energy', u'http://www.novinite.com/services/news_rdf.php?category_id=16'),
(u'Industry', u'http://www.novinite.com/services/news_rdf.php?category_id=17'),
(u'Properties', u'http://www.novinite.com/services/news_rdf.php?category_id=18'),
(u'Politics', u'http://www.novinite.com/services/news_rdf.php?category_id=2'),
(u'Diplomacy', u'http://www.novinite.com/services/news_rdf.php?category_id=20'),
(u'Defense', u'http://www.novinite.com/services/news_rdf.php?category_id=21'),
(u'Bulgaria in EU', u'http://www.novinite.com/services/news_rdf.php?category_id=22'),
(u'Domestic', u'http://www.novinite.com/services/news_rdf.php?category_id=23'),
(u'Society', u'http://www.novinite.com/services/news_rdf.php?category_id=3'),
(u'Environment', u'http://www.novinite.com/services/news_rdf.php?category_id=24'),
(u'Education', u'http://www.novinite.com/services/news_rdf.php?category_id=25'),
(u'Culture', u'http://www.novinite.com/services/news_rdf.php?category_id=26'),
(u'Archaeology', u'http://www.novinite.com/services/news_rdf.php?category_id=34'),
(u'Health', u'http://www.novinite.com/services/news_rdf.php?category_id=62'),
(u'Sports', u'http://www.novinite.com/services/news_rdf.php?category_id=4'),
(u'Crime', u'http://www.novinite.com/services/news_rdf.php?category_id=5'),
(u'Lifestyle', u'http://www.novinite.com/services/news_rdf.php?category_id=6'),
(u'World', u'http://www.novinite.com/services/news_rdf.php?category_id=30')]

View File

@ -0,0 +1,43 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Oscar Megia Lopez'
'''
perezreverte.com
'''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class PerezReverte(BasicNewsRecipe):
title = u'Patente de Corso'
__author__ = 'Oscar Megia Lopez'
description = 'Arturo Perez Reverte'
oldest_article = 90
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
encoding = 'utf8'
publisher = 'Arturo Perez Reverte'
category = 'Articulo'
language = 'es'
publication_type = 'Magazine'
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif } .contentheading{font-weight: bold} .txt_articulo{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} '
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
,'linearize_tables': True
}
keep_only_tags = [
dict(name='h2', attrs={'class':['titular']}),
dict(name='p', attrs={'class':['fecha']}),
dict(name='div', attrs={'class':['bloqueTexto']})
]
remove_attributes = ['width','height']
feeds = [
('Patente de corso - Web oficial de Arturo Perez Reverte', 'http://www.perezreverte.com/rss/patentes-corso/')
]

View File

@ -31,15 +31,19 @@ class RzeczpospolitaRecipe(BasicNewsRecipe):
feeds.append(u'http://www.rp.pl/rss/8.html')
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'storyp'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'story'}))
remove_tags =[]
remove_tags.append(dict(name = 'div', attrs = {'id' : 'adk_0'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'socialTools'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'articleToolBoxTop'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'clr'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'share_bottom'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'copyright_law'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'recommendations'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'editorPicks'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'articleCopyrightText'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'articleCopyrightButton'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'articleToolBoxBottom'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'more'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'editorPicks'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'addRecommendation'}))
extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
@ -62,3 +66,4 @@ class RzeczpospolitaRecipe(BasicNewsRecipe):
forget, sep, index = rest.rpartition(',')
return start + '/' + index + '?print=tak'

View File

@ -0,0 +1,27 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1313555075(BasicNewsRecipe):
news = True
title = u'The Clinic'
__author__ = 'Alex Mitrani'
description = u'Online version of Chilean satirical weekly'
publisher = u'The Clinic'
category = 'news, politics, Chile, rss'
oldest_article = 7
max_articles_per_feed = 100
summary_length = 1000
language = 'es_CL'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
masthead_url = 'http://www.theclinic.cl/wp-content/themes/tc12m/css/ui/mainLogoTC-top.png'
remove_tags_before = dict(name='article', attrs={'class':'scope bordered'})
remove_tags_after = dict(name='div', attrs={'id':'commentsSection'})
remove_tags = [dict(name='span', attrs={'class':'relTags'})
,dict(name='div', attrs={'class':'articleActivity hdcol'})
,dict(name='div', attrs={'id':'commentsSection'})
]
feeds = [(u'The Clinic Online', u'http://www.theclinic.cl/feed/')]

View File

@ -38,6 +38,7 @@ class WallStreetJournal(BasicNewsRecipe):
dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow","articleTabs_tab_quotes","articleTabs_tab_document"]),
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
dict(rel='shortcut icon'),
{'class':lambda x: x and 'sTools' in x},
]
remove_tags_after = [dict(id="article_story_body"), {'class':"article story"},]

View File

@ -40,6 +40,7 @@ class WallStreetJournal(BasicNewsRecipe):
dict(name='div', attrs={'data-flash-settings':True}),
{'class':['insetContent embedType-interactive insetCol3wide','insetCol6wide','insettipUnit']},
dict(rel='shortcut icon'),
{'class':lambda x: x and 'sTools' in x},
]
remove_tags_after = [dict(id="article_story_body"), {'class':"article story"},]

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" type="text/css" href="{prefix}/static/browse/browse.css" />
<link type="text/css" href="{prefix}/static/jquery_ui/css/humanity-custom/jquery-ui-1.8.5.custom.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="{prefix}/static/jquery.multiselect.css" />
<link rel="apple-touch-icon" href="/static/calibre.png" />
<link rel="apple-touch-icon" href="{prefix}/static/calibre.png" />
<script type="text/javascript" src="{prefix}/static/jquery.js"></script>
<script type="text/javascript" src="{prefix}/static/jquery.corner.js"></script>

View File

@ -179,6 +179,9 @@ save_template_title_series_sorting = 'library_order'
# changed. Changes to this tweak won't have an effect until the book is modified
# in some way. If you enter an invalid pattern, it is silently ignored.
# To disable use the expression: '^$'
# This expression is designed for articles that are followed by spaces. If you
# also need to match articles that are followed by other characters, for example L'
# in French, use: r"^(A\s+|The\s+|An\s+|L')" instead.
# Default: '^(A|The|An)\s+'
title_sort_articles=r'^(A|The|An)\s+'

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resources/images/random.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -21,3 +21,5 @@ vipy.session.add_content_browser('.r', ',r', 'Recipe',
vipy.session.glob_based_iterator(os.path.join(project_dir, 'recipes', '*.recipe')),
vipy.session.regexp_based_matcher(r'title\s*=\s*(?P<title>.+)', 'title', recipe_title_callback))
EOFPY
nmap \log :enew<CR>:read ! bzr log -l 500 ../.. <CR>:e ../../Changelog.yaml<CR>:e constants.py<CR>

2169
setup/iso639.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -205,8 +205,8 @@ class Resources(Command):
dest = self.j(self.RESOURCES, 'template-functions.json')
function_dict = {}
import inspect
from calibre.utils.formatter_functions import all_builtin_functions
for obj in all_builtin_functions:
from calibre.utils.formatter_functions import formatter_functions
for obj in formatter_functions.get_builtins().values():
eval_func = inspect.getmembers(obj,
lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate')
try:

View File

@ -273,10 +273,9 @@ class GetTranslations(Translations):
class ISO639(Command):
description = 'Compile translations for ISO 639 codes'
XML = '/usr/lib/python2.7/site-packages/pycountry/databases/iso639.xml'
def run(self, opts):
src = self.XML
src = self.j(self.d(self.SRC), 'setup', 'iso639.xml')
if not os.path.exists(src):
raise Exception(src + ' does not exist')
dest = self.j(self.d(self.SRC), 'resources', 'localization',
@ -290,20 +289,36 @@ class ISO639(Command):
by_2 = {}
by_3b = {}
by_3t = {}
m2to3 = {}
m3to2 = {}
m3bto3t = {}
nm = {}
codes2, codes3t, codes3b = set([]), set([]), set([])
for x in root.xpath('//iso_639_entry'):
name = x.get('name')
two = x.get('iso_639_1_code', None)
threeb = x.get('iso_639_2B_code')
threet = x.get('iso_639_2T_code')
if two is not None:
by_2[two] = name
codes2.add(two)
by_3b[x.get('iso_639_2B_code')] = name
by_3t[x.get('iso_639_2T_code')] = name
m2to3[two] = threet
m3to2[threeb] = m3to2[threet] = two
by_3b[threeb] = name
by_3t[threet] = name
if threeb != threet:
m3bto3t[threeb] = threet
codes3b.add(x.get('iso_639_2B_code'))
codes3t.add(x.get('iso_639_2T_code'))
base_name = name.lower()
nm[base_name] = threet
simple_name = base_name.partition(';')[0].strip()
if simple_name not in nm:
nm[simple_name] = threet
from cPickle import dump
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
'codes3b':codes3b, 'codes3t':codes3t}
'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3,
'3to2':m3to2, '3bto3t':m3bto3t, 'name_map':nm}
dump(x, open(dest, 'wb'), -1)

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 8, 12)
numeric_version = (0, 8, 14)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -590,8 +590,9 @@ from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary
from calibre.ebooks.metadata.sources.isbndb import ISBNDB
from calibre.ebooks.metadata.sources.overdrive import OverDrive
from calibre.ebooks.metadata.sources.douban import Douban
from calibre.ebooks.metadata.sources.ozon import Ozon
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive, Douban]
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive, Douban, Ozon]
# }}}
@ -843,6 +844,12 @@ class ActionNextMatch(InterfaceActionBase):
description = _('Find the next or previous match when searching in '
'your calibre library in highlight mode')
class ActionPickRandom(InterfaceActionBase):
name = 'Pick Random Book'
actual_plugin = 'calibre.gui2.actions.random:PickRandomAction'
description = _('Choose a random book from your calibre library')
class ActionStore(InterfaceActionBase):
name = 'Store'
author = 'John Schember'
@ -873,7 +880,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore,
ActionPluginUpdater]
ActionPluginUpdater, ActionPickRandom]
# }}}
@ -1023,7 +1030,7 @@ class TemplateFunctions(PreferencesPlugin):
category = 'Advanced'
gui_category = _('Advanced')
category_order = 5
name_order = 4
name_order = 5
config_widget = 'calibre.gui2.preferences.template_functions'
description = _('Create your own template functions')
@ -1086,6 +1093,17 @@ class Tweaks(PreferencesPlugin):
config_widget = 'calibre.gui2.preferences.tweaks'
description = _('Fine tune how calibre behaves in various contexts')
class Keyboard(PreferencesPlugin):
name = 'Keyboard'
icon = I('keyboard-prefs.png')
gui_name = _('Keyboard')
category = 'Advanced'
gui_category = _('Advanced')
category_order = 5
name_order = 4
config_widget = 'calibre.gui2.preferences.keyboard'
description = _('Customize the keyboard shortcuts used by calibre')
class Misc(PreferencesPlugin):
name = 'Misc'
icon = I('exec.png')
@ -1100,7 +1118,7 @@ class Misc(PreferencesPlugin):
plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions,
MetadataSources]
MetadataSources, Keyboard]
#}}}
@ -1470,6 +1488,14 @@ class StoreWoblinkStore(StoreBase):
headquarters = 'PL'
formats = ['EPUB']
class XinXiiStore(StoreBase):
name = 'XinXii'
description = ''
actual_plugin = 'calibre.gui2.store.stores.xinxii_plugin:XinXiiStore'
headquarters = 'DE'
formats = ['EPUB', 'PDF']
class StoreZixoStore(StoreBase):
name = 'Zixo'
author = u'Tomasz Długosz'
@ -1519,6 +1545,7 @@ plugins += [
StoreWHSmithUKStore,
StoreWizardsTowerBooksStore,
StoreWoblinkStore,
XinXiiStore,
StoreZixoStore
]

View File

@ -40,6 +40,7 @@ class ANDROID(USBMS):
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
0x7086 : [0x0226], 0x70a8: [0x9999], 0x42c4 : [0x216],
0x70c6 : [0x226]
},
# Sony Ericsson
@ -47,7 +48,7 @@ class ANDROID(USBMS):
# Google
0x18d1 : {
0x0001 : [0x0223],
0x0001 : [0x0223, 0x9999],
0x4e11 : [0x0100, 0x226, 0x227],
0x4e12 : [0x0100, 0x226, 0x227],
0x4e21 : [0x0100, 0x226, 0x227],
@ -63,6 +64,7 @@ class ANDROID(USBMS):
0x6860 : [0x0400],
0x6877 : [0x0400],
0x689e : [0x0400],
0xdeed : [0x0222],
},
# Viewsonic
@ -75,8 +77,11 @@ class ANDROID(USBMS):
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},
# LG
0x1004 : { 0x61cc : [0x100], 0x61ce : [0x100], 0x618e : [0x226,
0x9999] },
0x1004 : {
0x61cc : [0x100],
0x61ce : [0x100],
0x618e : [0x226, 0x9999, 0x100]
},
# Archos
0x0e79 : {
@ -128,11 +133,11 @@ class ANDROID(USBMS):
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK',
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD']
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
'__UMS_COMPOSITE', 'SGH-I997_CARD']
'__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870']
OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -59,7 +59,7 @@ class BAMBOOK(DeviceConfig, DevicePlugin):
def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None) :
self.open()
self.open(None)
def open(self, library_uuid):
# Make sure the Bambook library is ready

View File

@ -6,6 +6,7 @@ Created on 15 May 2010
import os
from calibre.devices.usbms.driver import USBMS, BookList
from calibre.ebooks import BOOK_EXTENSIONS
# This class is added to the standard device plugin chain, so that it can
# be configured. It has invalid vendor_id etc, so it will never match a
@ -16,8 +17,8 @@ class FOLDER_DEVICE_FOR_CONFIG(USBMS):
description = _('Use an arbitrary folder as a device.')
author = 'John Schember/Charles Haley'
supported_platforms = ['windows', 'osx', 'linux']
FORMATS = ['epub', 'fb2', 'mobi', 'azw', 'lrf', 'tcr', 'pmlz', 'lit',
'rtf', 'rb', 'pdf', 'oeb', 'txt', 'pdb', 'prc']
FORMATS = list(BOOK_EXTENSIONS)
VENDOR_ID = [0xffff]
PRODUCT_ID = [0xffff]
BCD = [0xffff]

View File

@ -64,7 +64,7 @@ class KINDLE(USBMS):
EBOOK_DIR_MAIN = 'documents'
EBOOK_DIR_CARD_A = 'documents'
DELETE_EXTS = ['.mbp','.tan','.pdr']
DELETE_EXTS = ['.mbp', '.tan', '.pdr', '.ea', '.apnx', '.phl']
SUPPORTS_SUB_DIRS = True
SUPPORTS_ANNOTATIONS = True

View File

@ -333,8 +333,14 @@ class KOBO(USBMS):
except Exception as e:
if 'no such column' not in str(e):
raise
try:
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
'where BookID is Null and ContentID =?',t)
except Exception as e:
if 'no such column' not in str(e):
raise
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
'where BookID is Null and ContentID =?',t)
connection.commit()

View File

@ -252,8 +252,8 @@ class EEEREADER(USBMS):
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Book'
VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET'
VENDOR_NAME = ['LINUX', 'ASUS']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['FILE-STOR_GADGET', 'EEE_NOTE']
class ADAM(USBMS):

View File

@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil, time
from calibre.devices.errors import PathError
from calibre.utils.filenames import case_preserving_open_file
class File(object):
@ -46,10 +47,8 @@ class CLI(object):
path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path):
raise PathError('File already exists: ' + path)
d = os.path.dirname(path)
if not os.path.exists(d):
os.makedirs(d)
with open(path, 'w+b') as dest:
dest, actual_path = case_preserving_open_file(path)
with dest:
try:
shutil.copyfileobj(infile, dest)
except IOError:
@ -62,6 +61,7 @@ class CLI(object):
#if not check_transfer(infile, dest): raise Exception('Transfer failed')
if close:
infile.close()
return actual_path
def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or \

View File

@ -258,10 +258,10 @@ class USBMS(CLI, Device):
for i, infile in enumerate(files):
mdata, fname = metadata.next(), names.next()
filepath = self.normalize_path(self.create_upload_path(path, mdata, fname))
paths.append(filepath)
if not hasattr(infile, 'read'):
infile = self.normalize_path(infile)
self.put_file(infile, filepath, replace_file=True)
filepath = self.put_file(infile, filepath, replace_file=True)
paths.append(filepath)
try:
self.upload_cover(os.path.dirname(filepath),
os.path.splitext(os.path.basename(filepath))[0],

View File

@ -8,6 +8,7 @@ from various formats.
'''
import traceback, os, re
from cStringIO import StringIO
from calibre import CurrentDir
class ConversionError(Exception):
@ -27,8 +28,9 @@ class ParserError(ValueError):
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'text', 'htm', 'xhtm',
'html', 'htmlz', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc',
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip',
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb']
'epub', 'fb2', 'djv', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip',
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb',
'xps', 'oxps']
class HTMLRenderer(object):
@ -209,4 +211,45 @@ def unit_convert(value, base, font, dpi):
result = value * 0.40
return result
def generate_masthead(title, output_path=None, width=600, height=60):
from calibre.ebooks.conversion.config import load_defaults
from calibre.utils.fonts import fontconfig
font_path = default_font = P('fonts/liberation/LiberationSerif-Bold.ttf')
recs = load_defaults('mobi_output')
masthead_font_family = recs.get('masthead_font', 'Default')
if masthead_font_family != 'Default':
masthead_font = fontconfig.files_for_family(masthead_font_family)
# Assume 'normal' always in dict, else use default
# {'normal': (path_to_font, friendly name)}
if 'normal' in masthead_font:
font_path = masthead_font['normal'][0]
if not font_path or not os.access(font_path, os.R_OK):
font_path = default_font
try:
from PIL import Image, ImageDraw, ImageFont
Image, ImageDraw, ImageFont
except ImportError:
import Image, ImageDraw, ImageFont
img = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype(font_path, 48)
except:
font = ImageFont.truetype(default_font, 48)
text = title.encode('utf-8')
width, height = draw.textsize(text, font=font)
left = max(int((width - width)/2.), 0)
top = max(int((height - height)/2.), 0)
draw.text((left, top), text, fill=(0,0,0), font=font)
if output_path is None:
f = StringIO()
img.save(f, 'JPEG')
return f.getvalue()
else:
with open(output_path, 'wb') as f:
img.save(f, 'JPEG')

View File

@ -557,7 +557,7 @@ OptionRecommendation(name='delete_blank_paragraphs',
OptionRecommendation(name='format_scene_breaks',
recommended_value=True, level=OptionRecommendation.LOW,
help=_('Left aligned scene break markers are center aligned. '
'Replace soft scene breaks that use multiple blank lines with'
'Replace soft scene breaks that use multiple blank lines with '
'horizontal rules.')),
OptionRecommendation(name='replace_scene_breaks',

View File

@ -47,8 +47,7 @@ PUBLICATION_METADATA_FIELDS = frozenset([
# If None, means book
'publication_type',
'uuid', # A UUID usually of type 4
'language', # the primary language of this book
'languages', # ordered list
'languages', # ordered list of languages in this publication
'publisher', # Simple string, no special semantics
# Absolute path to image file encoded in filesystem_encoding
'cover',
@ -109,7 +108,7 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
# Metadata fields that smart update must do special processing to copy.
SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map',
'cover_data', 'tags', 'language',
'cover_data', 'tags', 'languages',
'identifiers'])
# Metadata fields that smart update should copy only if the source is not None

View File

@ -102,6 +102,7 @@ class Metadata(object):
@param other: None or a metadata object
'''
_data = copy.deepcopy(NULL_VALUES)
_data.pop('language')
object.__setattr__(self, '_data', _data)
if other is not None:
self.smart_update(other)
@ -136,6 +137,11 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
if field in TOP_LEVEL_IDENTIFIERS:
return _data.get('identifiers').get(field, None)
if field == 'language':
try:
return _data.get('languages', [])[0]
except:
return NULL_VALUES['language']
if field in STANDARD_METADATA_FIELDS:
return _data.get(field, None)
try:
@ -175,6 +181,11 @@ class Metadata(object):
if not val:
val = copy.copy(NULL_VALUES.get('identifiers', None))
self.set_identifiers(val)
elif field == 'language':
langs = []
if val and val.lower() != 'und':
langs = [val]
_data['languages'] = langs
elif field in STANDARD_METADATA_FIELDS:
if val is None:
val = copy.copy(NULL_VALUES.get(field, None))
@ -553,9 +564,9 @@ class Metadata(object):
for attr in TOP_LEVEL_IDENTIFIERS:
copy_not_none(self, other, attr)
other_lang = getattr(other, 'language', None)
if other_lang and other_lang.lower() != 'und':
self.language = other_lang
other_lang = getattr(other, 'languages', [])
if other_lang and other_lang != ['und']:
self.languages = list(other_lang)
if not getattr(self, 'series', None):
self.series_index = None
@ -706,8 +717,8 @@ class Metadata(object):
fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
if self.series:
fmt('Series', self.series + ' #%s'%self.format_series_index())
if not self.is_null('language'):
fmt('Language', self.language)
if not self.is_null('languages'):
fmt('Languages', ', '.join(self.languages))
if self.rating is not None:
fmt('Rating', self.rating)
if self.timestamp is not None:
@ -743,7 +754,7 @@ class Metadata(object):
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
if self.series:
ans += [(_('Series'), unicode(self.series) + ' #%s'%self.format_series_index())]
ans += [(_('Language'), unicode(self.language))]
ans += [(_('Languages'), u', '.join(self.languages))]
if self.timestamp is not None:
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
if self.pubdate is not None:

View File

@ -11,7 +11,7 @@ from functools import partial
from base64 import b64decode
from lxml import etree
from calibre.utils.date import parse_date
from calibre import guess_all_extensions, prints, force_unicode
from calibre import guess_type, guess_all_extensions, prints, force_unicode
from calibre.ebooks.metadata import MetaInformation, check_isbn
from calibre.ebooks.chardet import xml_to_unicode
@ -147,6 +147,15 @@ def _parse_cover_data(root, imgid, mi):
if elm_binary:
mimetype = elm_binary[0].get('content-type', 'image/jpeg')
mime_extensions = guess_all_extensions(mimetype)
if not mime_extensions and mimetype.startswith('image/'):
prints("WARNING: Unsupported or misspelled mime-type '%s'. "\
"Trying to recovery mime-type from id_ref='%s'" % (mimetype, imgid) )
ctype = guess_type(imgid) # -> (mime-type, encoding)
mimetype_fromid = ctype[0]
if mimetype_fromid and mimetype_fromid.startswith('image/'):
mime_extensions = guess_all_extensions(mimetype_fromid)
if mime_extensions:
pic_data = elm_binary[0].text
if pic_data:

View File

@ -38,17 +38,17 @@ def get_metadata_(src, encoding=None):
if match:
title = match.group(2)
else:
pat = re.compile('<title>([^<>]+?)</title>', re.IGNORECASE)
match = pat.search(src)
if match:
title = match.group(1)
if not title:
for x in ('Title','DC.title','DCTERMS.title'):
for x in ('DC.title','DCTERMS.title','Title'):
pat = get_meta_regexp_(x)
match = pat.search(src)
if match:
title = match.group(1)
break
if not title:
pat = re.compile('<title>([^<>]+?)</title>', re.IGNORECASE)
match = pat.search(src)
if match:
title = match.group(1)
# Author
author = None
@ -57,7 +57,7 @@ def get_metadata_(src, encoding=None):
if match:
author = match.group(2).replace(',', ';')
else:
for x in ('Author','DC.creator.aut','DCTERMS.creator.aut'):
for x in ('Author','DC.creator.aut','DCTERMS.creator.aut', 'DC.creator'):
pat = get_meta_regexp_(x)
match = pat.search(src)
if match:

View File

@ -67,10 +67,6 @@ def _metadata_from_formats(formats, force_read_metadata=False, pattern=None):
return mi
def is_recipe(filename):
return filename.startswith('calibre') and \
filename.rpartition('.')[0].endswith('_recipe_out')
def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False,
force_read_metadata=False, pattern=None):
pos = 0
@ -106,7 +102,7 @@ def _get_metadata(stream, stream_type, use_libprs_metadata,
mi = MetaInformation(None, None)
name = os.path.basename(getattr(stream, 'name', ''))
base = metadata_from_filename(name, pat=pattern)
if force_read_metadata or is_recipe(name) or prefs['read_file_metadata']:
if force_read_metadata or prefs['read_file_metadata']:
mi = get_file_type_metadata(stream, stream_type)
if base.title == os.path.splitext(name)[0] and \
base.is_null('authors') and base.is_null('isbn'):

View File

@ -19,7 +19,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang
from calibre.utils.localization import get_lang, canonicalize_lang
from calibre import prints, guess_type
from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.config import tweaks
@ -515,6 +515,7 @@ class OPF(object): # {{{
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
'(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]')
languages_path = XPath('descendant::*[local-name()="language"]')
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
@ -523,7 +524,6 @@ class OPF(object): # {{{
title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x))
publisher = MetadataField('publisher')
language = MetadataField('language')
comments = MetadataField('description')
category = MetadataField('type')
rights = MetadataField('rights')
@ -930,6 +930,44 @@ class OPF(object): # {{{
return property(fget=fget, fset=fset)
@dynamic_property
def language(self):
def fget(self):
ans = self.languages
if ans:
return ans[0]
def fset(self, val):
self.languages = [val]
return property(fget=fget, fset=fset)
@dynamic_property
def languages(self):
def fget(self):
ans = []
for match in self.languages_path(self.metadata):
t = self.get_text(match)
if t and t.strip():
l = canonicalize_lang(t.strip())
if l:
ans.append(l)
return ans
def fset(self, val):
matches = self.languages_path(self.metadata)
for x in matches:
x.getparent().remove(x)
for lang in val:
l = self.create_metadata_element('language')
self.set_text(l, unicode(lang))
return property(fget=fget, fset=fset)
@dynamic_property
def book_producer(self):
@ -1052,9 +1090,9 @@ class OPF(object): # {{{
val = getattr(mi, attr, None)
if val is not None and val != [] and val != (None, None):
setattr(self, attr, val)
lang = getattr(mi, 'language', None)
if lang and lang != 'und':
self.language = lang
langs = getattr(mi, 'languages', [])
if langs and langs != ['und']:
self.languages = langs
temp = self.to_book_metadata()
temp.smart_update(mi, replace_metadata=replace_metadata)
self._user_metadata_ = temp.get_all_user_metadata(True)
@ -1202,9 +1240,10 @@ class OPFCreator(Metadata):
dc_attrs={'id':__appname__+'_id'}))
if getattr(self, 'pubdate', None) is not None:
a(DC_ELEM('date', self.pubdate.isoformat()))
lang = self.language
if not lang or lang.lower() == 'und':
lang = get_lang().replace('_', '-')
langs = self.languages
if not langs or langs == ['und']:
langs = [get_lang().replace('_', '-').partition('-')[0]]
for lang in langs:
a(DC_ELEM('language', lang))
if self.comments:
a(DC_ELEM('description', self.comments))
@ -1288,8 +1327,9 @@ def metadata_to_opf(mi, as_string=True):
mi.book_producer = __appname__ + ' (%s) '%__version__ + \
'[http://calibre-ebook.com]'
if not mi.language:
mi.language = 'UND'
if not mi.languages:
lang = get_lang().replace('_', '-').partition('-')[0]
mi.languages = [lang]
root = etree.fromstring(textwrap.dedent(
'''
@ -1339,8 +1379,10 @@ def metadata_to_opf(mi, as_string=True):
factory(DC('identifier'), val, scheme=icu_upper(key))
if mi.rights:
factory(DC('rights'), mi.rights)
factory(DC('language'), mi.language if mi.language and mi.language.lower()
!= 'und' else get_lang().replace('_', '-'))
for lang in mi.languages:
if not lang or lang.lower() == 'und':
continue
factory(DC('language'), lang)
if mi.tags:
for tag in mi.tags:
factory(DC('subject'), tag)

View File

@ -22,6 +22,7 @@ from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.metadata.book.base import Metadata
from calibre.library.comments import sanitize_comments_html
from calibre.utils.date import parse_date
from calibre.utils.localization import canonicalize_lang
class Worker(Thread): # Get details {{{
@ -106,10 +107,11 @@ class Worker(Thread): # Get details {{{
r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}')
lm = {
'en': ('English', 'Englisch'),
'fr': ('French', 'Français'),
'it': ('Italian', 'Italiano'),
'de': ('German', 'Deutsch'),
'eng': ('English', 'Englisch'),
'fra': ('French', 'Français'),
'ita': ('Italian', 'Italiano'),
'deu': ('German', 'Deutsch'),
'spa': ('Spanish', 'Espa\xf1ol', 'Espaniol'),
}
self.lang_map = {}
for code, names in lm.iteritems():
@ -374,8 +376,11 @@ class Worker(Thread): # Get details {{{
def parse_language(self, pd):
for x in reversed(pd.xpath(self.language_xpath)):
if x.tail:
ans = x.tail.strip()
ans = self.lang_map.get(ans, None)
raw = x.tail.strip()
ans = self.lang_map.get(raw, None)
if ans:
return ans
ans = canonicalize_lang(ans)
if ans:
return ans
# }}}
@ -388,7 +393,7 @@ class Amazon(Source):
capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'identifier:amazon',
'identifier:isbn', 'rating', 'comments', 'publisher', 'pubdate',
'language'])
'languages'])
has_html_comments = True
supports_gzip_transfer_encoding = True

View File

@ -20,6 +20,7 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.chardet import xml_to_unicode
from calibre.utils.date import parse_date, utcnow
from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.localization import canonicalize_lang
from calibre import as_unicode
NAMESPACES = {
@ -95,7 +96,9 @@ def to_metadata(browser, log, entry_, timeout): # {{{
return mi
mi.comments = get_text(extra, description)
#mi.language = get_text(extra, language)
lang = canonicalize_lang(get_text(extra, language))
if lang:
mi.language = lang
mi.publisher = get_text(extra, publisher)
# ISBN
@ -162,7 +165,7 @@ class GoogleBooks(Source):
capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
'comments', 'publisher', 'identifier:isbn', 'rating',
'identifier:google']) # language currently disabled
'identifier:google', 'languages'])
supports_gzip_transfer_encoding = True
cached_cover_url_is_reliable = False

View File

@ -484,6 +484,7 @@ def identify(log, abort, # {{{
'publication dates')
start_time = time.time()
results = merge_identify_results(results, log)
log('We have %d merged results, merging took: %.2f seconds' %
(len(results), time.time() - start_time))

View File

@ -35,7 +35,7 @@ class ISBNDB(Source):
options = (
Option('isbndb_key', 'string', None, _('IsbnDB key:'),
_('To use isbndb.com you have to sign up for a free account'
_('To use isbndb.com you have to sign up for a free account '
'at isbndb.com and get an access key.')),
)

View File

@ -35,7 +35,7 @@ class OverDrive(Source):
capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
'comments', 'publisher', 'identifier:isbn', 'series', 'series_index',
'language', 'identifier:overdrive'])
'languages', 'identifier:overdrive'])
has_html_comments = True
supports_gzip_transfer_encoding = False
cached_cover_url_is_reliable = True
@ -421,8 +421,10 @@ class OverDrive(Source):
pass
if lang:
lang = lang[0].strip().lower()
mi.language = {'english':'en', 'french':'fr', 'german':'de',
'spanish':'es'}.get(lang, None)
lang = {'english':'eng', 'french':'fra', 'german':'deu',
'spanish':'spa'}.get(lang, None)
if lang:
mi.language = lang
if ebook_isbn:
#print "ebook isbn is "+str(ebook_isbn[0])

View File

@ -0,0 +1,445 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
from xml.etree.ElementTree import _Element
__license__ = 'GPL 3'
__copyright__ = '2011, Roman Mukhin <ramses_ru at hotmail.com>'
__docformat__ = 'restructuredtext en'
import re
import urllib2
import datetime
from urllib import quote_plus
from Queue import Queue, Empty
from lxml import etree, html
from lxml.etree import ElementBase
from calibre import as_unicode
from calibre import prints
from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import Source
from calibre.ebooks.metadata.book.base import Metadata
class Ozon(Source):
name = 'OZON.ru'
description = _('Downloads metadata and covers from OZON.ru')
capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'identifier:isbn', 'identifier:ozon',
'publisher', 'pubdate', 'comments', 'series', 'rating', 'language'])
# Test purpose only, test function does not like when sometimes some filed are empty
#touched_fields = frozenset(['title', 'authors', 'identifier:isbn', 'identifier:ozon',
# 'publisher', 'pubdate', 'comments'])
supports_gzip_transfer_encoding = True
has_html_comments = True
ozon_url = 'http://www.ozon.ru'
# match any ISBN10/13. From "Regular Expressions Cookbook"
isbnPattern = r'(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}|'\
'[-0-9X ]{13}|[0-9X]{10})(?:97[89][- ]?)?[0-9]{1,5}[- ]?'\
'(?:[0-9]+[- ]?){2}[0-9X]'
isbnRegex = re.compile(isbnPattern)
def get_book_url(self, identifiers): # {{{
ozon_id = identifiers.get('ozon', None)
res = None
if ozon_id:
url = '{}/context/detail/id/{}?partner={}'.format(self.ozon_url, urllib2.quote(ozon_id), _get_affiliateId())
res = ('ozon', ozon_id, url)
return res
# }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
# div_book -> search only books, ebooks and audio books
search_url = self.ozon_url + '/webservice/webservice.asmx/SearchWebService?searchContext=div_book&searchText='
isbn = _format_isbn(log, identifiers.get('isbn', None))
# TODO: format isbn!
qItems = set([isbn, title])
if authors:
qItems |= frozenset(authors)
qItems.discard(None)
qItems.discard('')
qItems = map(_quoteString, qItems)
q = ' '.join(qItems).strip()
log.info(u'search string: ' + q)
if isinstance(q, unicode):
q = q.encode('utf-8')
if not q:
return None
search_url += quote_plus(q)
log.debug(u'search url: %r'%search_url)
return search_url
# }}}
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
identifiers={}, timeout=30):
if not self.is_configured():
return
query = self.create_query(log, title=title, authors=authors, identifiers=identifiers)
if not query:
err = 'Insufficient metadata to construct query'
log.error(err)
return err
try:
raw = self.browser.open_novisit(query).read()
except Exception as e:
log.exception(u'Failed to make identify query: %r'%query)
return as_unicode(e)
try:
parser = etree.XMLParser(recover=True, no_network=True)
feed = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0], parser=parser)
entries = feed.xpath('//*[local-name() = "SearchItems"]')
if entries:
metadata = self.get_metadata(log, entries, title, authors, identifiers)
self.get_all_details(log, metadata, abort, result_queue, identifiers, timeout)
except Exception as e:
log.exception('Failed to parse identify results')
return as_unicode(e)
# }}}
def get_metadata(self, log, entries, title, authors, identifiers): # {{{
title = unicode(title).upper() if title else ''
authors = map(unicode.upper, map(unicode, authors)) if authors else None
ozon_id = identifiers.get('ozon', None)
unk = unicode(_('Unknown')).upper()
if title == unk:
title = None
if authors == [unk]:
authors = None
def in_authors(authors, miauthors):
for author in authors:
for miauthor in miauthors:
if author in miauthor: return True
return None
def ensure_metadata_match(mi): # {{{
match = True
if title:
mititle = unicode(mi.title).upper() if mi.title else ''
match = title in mititle
if match and authors:
miauthors = map(unicode.upper, map(unicode, mi.authors)) if mi.authors else []
match = in_authors(authors, miauthors)
if match and ozon_id:
mozon_id = mi.identifiers['ozon']
match = ozon_id == mozon_id
return match
metadata = []
for i, entry in enumerate(entries):
mi = self.to_metadata(log, entry)
mi.source_relevance = i
if ensure_metadata_match(mi):
metadata.append(mi)
# log.debug(u'added metadata %s %s. '%(mi.title, mi.authors))
else:
log.debug(u'skipped metadata %s %s. (does not match the query)'%(mi.title, mi.authors))
return metadata
# }}}
def get_all_details(self, log, metadata, abort, result_queue, identifiers, timeout): # {{{
req_isbn = identifiers.get('isbn', None)
for mi in metadata:
if abort.is_set():
break
try:
ozon_id = mi.identifiers['ozon']
try:
self.get_book_details(log, mi, timeout)
except:
log.exception(u'Failed to get details for metadata: %s'%mi.title)
all_isbns = getattr(mi, 'all_isbns', [])
if req_isbn and all_isbns and check_isbn(req_isbn) not in all_isbns:
log.debug(u'skipped, no requested ISBN %s found'%req_isbn)
continue
for isbn in all_isbns:
self.cache_isbn_to_identifier(isbn, ozon_id)
if mi.ozon_cover_url:
self.cache_identifier_to_cover_url(ozon_id, mi.ozon_cover_url)
self.clean_downloaded_metadata(mi)
result_queue.put(mi)
except:
log.exception(u'Failed to get details for metadata: %s'%mi.title)
# }}}
def to_metadata(self, log, entry): # {{{
xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())'
title = entry.xpath(xp_template.format('Name'))
author = entry.xpath(xp_template.format('Author'))
mi = Metadata(title, author.split(','))
ozon_id = entry.xpath(xp_template.format('ID'))
mi.identifiers = {'ozon':ozon_id}
mi.comments = entry.xpath(xp_template.format('Annotation'))
mi.ozon_cover_url = None
cover = entry.xpath(xp_template.format('Picture'))
if cover:
mi.ozon_cover_url = _translateToBigCoverUrl(cover)
rating = entry.xpath(xp_template.format('ClientRatingValue'))
if rating:
try:
#'rating', A floating point number between 0 and 10
# OZON raion N of 5, calibre of 10, but there is a bug? in identify
mi.rating = float(rating)
except:
pass
rating
return mi
# }}}
def get_cached_cover_url(self, identifiers): # {{{
url = None
ozon_id = identifiers.get('ozon', None)
if ozon_id is None:
isbn = identifiers.get('isbn', None)
if isbn is not None:
ozon_id = self.cached_isbn_to_identifier(isbn)
if ozon_id is not None:
url = self.cached_identifier_to_cover_url(ozon_id)
return url
# }}}
def download_cover(self, log, result_queue, abort, title=None, authors=None, identifiers={}, timeout=30): # {{{
cached_url = self.get_cached_cover_url(identifiers)
if cached_url is None:
log.debug('No cached cover found, running identify')
rq = Queue()
self.identify(log, rq, abort, title=title, authors=authors, identifiers=identifiers)
if abort.is_set():
return
results = []
while True:
try:
results.append(rq.get_nowait())
except Empty:
break
results.sort(key=self.identify_results_keygen(title=title, authors=authors, identifiers=identifiers))
for mi in results:
cached_url = self.get_cached_cover_url(mi.identifiers)
if cached_url is not None:
break
if cached_url is None:
log.info('No cover found')
return
if abort.is_set():
return
log.debug('Downloading cover from:', cached_url)
try:
cdata = self.browser.open_novisit(cached_url, timeout=timeout).read()
if cdata:
result_queue.put((self, cdata))
except Exception as e:
log.exception(u'Failed to download cover from: %s'%cached_url)
return as_unicode(e)
# }}}
def get_book_details(self, log, metadata, timeout): # {{{
url = self.get_book_url(metadata.get_identifiers())[2]
raw = self.browser.open_novisit(url, timeout=timeout).read()
doc = html.fromstring(raw)
# series
xpt = u'normalize-space(//div[@class="frame_content"]//div[contains(normalize-space(text()), "Серия:")]//a/@title)'
series = doc.xpath(xpt)
if series:
metadata.series = series
xpt = u'substring-after(//meta[@name="description"]/@content, "ISBN")'
isbn_str = doc.xpath(xpt)
if isbn_str:
all_isbns = [check_isbn(isbn) for isbn in self.isbnRegex.findall(isbn_str) if check_isbn(isbn)]
if all_isbns:
metadata.all_isbns = all_isbns
metadata.isbn = all_isbns[0]
xpt = u'//div[@class="frame_content"]//div[contains(normalize-space(text()), "Издатель")]//a[@title="Издательство"]'
publishers = doc.xpath(xpt)
if publishers:
metadata.publisher = publishers[0].text
xpt = u'string(../text()[contains(., "г.")])'
yearIn = publishers[0].xpath(xpt)
if yearIn:
matcher = re.search(r'\d{4}', yearIn)
if matcher:
year = int(matcher.group(0))
# only year is available, so use 1-st of Jan
metadata.pubdate = datetime.datetime(year, 1, 1) #<- failed comparation in identify.py
#metadata.pubdate = datetime(year, 1, 1)
xpt = u'substring-after(string(../text()[contains(., "Язык")]), ": ")'
displLang = publishers[0].xpath(xpt)
lang_code =_translageLanguageToCode(displLang)
if lang_code:
metadata.language = lang_code
# overwrite comments from HTML if any
# tr/td[contains(.//text(), "От издателя")] -> does not work, why?
xpt = u'//div[contains(@class, "detail")]//tr/td//text()[contains(., "От издателя")]'\
u'/ancestor::tr[1]/following-sibling::tr[1]/td[contains(./@class, "description")][1]'
comment_elem = doc.xpath(xpt)
if comment_elem:
comments = unicode(etree.tostring(comment_elem[0]))
if comments:
# cleanup root tag, TODO: remove tags like object/embeded
comments = re.sub(r'^<td.+?>|</td>.+?$', u'', comments).strip()
if comments:
metadata.comments = comments
else:
log.debug('No book description found in HTML')
# }}}
def _quoteString(str): # {{{
return '"' + str + '"' if str and str.find(' ') != -1 else str
# }}}
# TODO: make customizable
def _translateToBigCoverUrl(coverUrl): # {{{
# http://www.ozon.ru/multimedia/books_covers/small/1002986468.gif
# http://www.ozon.ru/multimedia/books_covers/1002986468.jpg
m = re.match(r'^(.+\/)small\/(.+\.).+$', coverUrl)
if m:
coverUrl = m.group(1) + m.group(2) + 'jpg'
return coverUrl
# }}}
def _get_affiliateId(): # {{{
import random
aff_id = 'romuk'
# Use Kovid's affiliate id 30% of the time.
if random.randint(1, 10) in (1, 2, 3):
aff_id = 'kovidgoyal'
return aff_id
# }}}
# for now only RUS ISBN are supported
#http://ru.wikipedia.org/wiki/ISBN_российских_издательств
isbn_pat = re.compile(r"""
^
(\d{3})? # match GS1 Prefix for ISBN13
(5) # group identifier for rRussian-speaking countries
( # begin variable length for Publisher
[01]\d{1}| # 2x
[2-6]\d{2}| # 3x
7\d{3}| # 4x (starting with 7)
8[0-4]\d{2}| # 4x (starting with 8)
9[2567]\d{2}| # 4x (starting with 9)
99[26]\d{1}| # 4x (starting with 99)
8[5-9]\d{3}| # 5x (starting with 8)
9[348]\d{3}| # 5x (starting with 9)
900\d{2}| # 5x (starting with 900)
91[0-8]\d{2}| # 5x (starting with 91)
90[1-9]\d{3}| # 6x (starting with 90)
919\d{3}| # 6x (starting with 919)
99[^26]\d{4} # 7x (starting with 99)
) # end variable length for Publisher
(\d+) # Title
([\dX]) # Check digit
$
""", re.VERBOSE)
def _format_isbn(log, isbn): # {{{
res = check_isbn(isbn)
if res:
m = isbn_pat.match(res)
if m:
res = '-'.join([g for g in m.groups() if g])
else:
log.error('cannot format isbn %s'%isbn)
return res
# }}}
def _translageLanguageToCode(displayLang): # {{{
displayLang = unicode(displayLang).strip() if displayLang else None
langTbl = { None: 'ru',
u'Немецкий': 'de',
u'Английский': 'en',
u'Французский': 'fr',
u'Итальянский': 'it',
u'Испанский': 'es',
u'Китайский': 'zh',
u'Японский': 'ja' }
return langTbl.get(displayLang, None)
# }}}
if __name__ == '__main__': # tests {{{
# To run these test use: calibre-debug -e src/calibre/ebooks/metadata/sources/ozon.py
# comment some touched_fields before run thoses tests
from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
title_test, authors_test, isbn_test)
test_identify_plugin(Ozon.name,
[
(
{'identifiers':{'isbn': '9785916572629'} },
[title_test(u'На все четыре стороны', exact=True),
authors_test([u'А. А. Гилл'])]
),
(
{'identifiers':{}, 'title':u'Der Himmel Kennt Keine Gunstlinge',
'authors':[u'Erich Maria Remarque']},
[title_test(u'Der Himmel Kennt Keine Gunstlinge', exact=True),
authors_test([u'Erich Maria Remarque'])]
),
(
{'identifiers':{ }, 'title':u'Метро 2033',
'authors':[u'Дмитрий Глуховский']},
[title_test(u'Метро 2033', exact=False)]
),
(
{'identifiers':{'isbn': '9785170727209'}, 'title':u'Метро 2033',
'authors':[u'Дмитрий Глуховский']},
[title_test(u'Метро 2033', exact=True),
authors_test([u'Дмитрий Глуховский']),
isbn_test('9785170727209')]
),
(
{'identifiers':{'isbn': '5-699-13613-4'}, 'title':u'Метро 2033',
'authors':[u'Дмитрий Глуховский']},
[title_test(u'Метро 2033', exact=True),
authors_test([u'Дмитрий Глуховский'])]
),
(
{'identifiers':{}, 'title':u'Метро',
'authors':[u'Глуховский']},
[title_test(u'Метро', exact=False)]
),
])
# }}}

View File

@ -18,6 +18,11 @@ from calibre.ebooks.mobi.utils import (decode_hex_number, decint,
get_trailing_data, decode_tbs)
from calibre.utils.magick.draw import identify_data
def format_bytes(byts):
byts = bytearray(byts)
byts = [hex(b)[2:] for b in byts]
return ' '.join(byts)
# PalmDB {{{
class PalmDOCAttributes(object):
@ -76,7 +81,7 @@ class PalmDB(object):
self.ident = self.type + self.creator
if self.ident not in (b'BOOKMOBI', b'TEXTREAD'):
raise ValueError('Unknown book ident: %r'%self.ident)
self.uid_seed, = struct.unpack(b'>I', self.raw[68:72])
self.last_record_uid, = struct.unpack(b'>I', self.raw[68:72])
self.next_rec_list_id = self.raw[72:76]
self.number_of_records, = struct.unpack(b'>H', self.raw[76:78])
@ -97,7 +102,7 @@ class PalmDB(object):
ans.append('Sort Info ID: %r'%self.sort_info_id)
ans.append('Type: %r'%self.type)
ans.append('Creator: %r'%self.creator)
ans.append('UID seed: %r'%self.uid_seed)
ans.append('Last record UID +1: %r'%self.last_record_uid)
ans.append('Next record list id: %r'%self.next_rec_list_id)
ans.append('Number of records: %s'%self.number_of_records)
@ -272,7 +277,8 @@ class MOBIHeader(object): # {{{
self.first_image_index, = struct.unpack(b'>I', self.raw[108:112])
self.huffman_record_offset, = struct.unpack(b'>I', self.raw[112:116])
self.huffman_record_count, = struct.unpack(b'>I', self.raw[116:120])
self.unknown2 = self.raw[120:128]
self.datp_record_offset, = struct.unpack(b'>I', self.raw[120:124])
self.datp_record_count, = struct.unpack(b'>I', self.raw[124:128])
self.exth_flags, = struct.unpack(b'>I', self.raw[128:132])
self.has_exth = bool(self.exth_flags & 0x40)
self.has_drm_data = self.length >= 174 and len(self.raw) >= 180
@ -314,7 +320,7 @@ class MOBIHeader(object): # {{{
self.exth = EXTHHeader(self.raw[self.exth_offset:])
self.end_of_exth = self.exth_offset + self.exth.length
self.bytes_after_exth = self.fullname_offset - self.end_of_exth
self.bytes_after_exth = self.raw[self.end_of_exth:self.fullname_offset]
def __str__(self):
ans = ['*'*20 + ' MOBI Header '+ '*'*20]
@ -347,7 +353,8 @@ class MOBIHeader(object): # {{{
ans.append('First Image index: %d'%self.first_image_index)
ans.append('Huffman record offset: %d'%self.huffman_record_offset)
ans.append('Huffman record count: %d'%self.huffman_record_count)
ans.append('Unknown2: %r'%self.unknown2)
ans.append('DATP record offset: %r'%self.datp_record_offset)
ans.append('DATP record count: %r'%self.datp_record_count)
ans.append('EXTH flags: %s (%s)'%(bin(self.exth_flags)[2:], self.has_exth))
if self.has_drm_data:
ans.append('Unknown3: %r'%self.unknown3)
@ -379,7 +386,9 @@ class MOBIHeader(object): # {{{
if self.has_exth:
ans += '\n\n' + str(self.exth)
ans += '\n\nBytes after EXTH: %d'%self.bytes_after_exth
ans += '\n\nBytes after EXTH (%d bytes): %s'%(
len(self.bytes_after_exth),
format_bytes(self.bytes_after_exth))
ans += '\nNumber of bytes after full name: %d' % (len(self.raw) - (self.fullname_offset +
self.fullname_length))
@ -406,6 +415,109 @@ class TagX(object): # {{{
self.num_values, bin(self.bitmask), self.eof)
# }}}
class SecondaryIndexHeader(object): # {{{
def __init__(self, record):
self.record = record
raw = self.record.raw
#open('/t/index_header.bin', 'wb').write(raw)
if raw[:4] != b'INDX':
raise ValueError('Invalid Secondary Index Record')
self.header_length, = struct.unpack('>I', raw[4:8])
self.unknown1 = raw[8:16]
self.index_type, = struct.unpack('>I', raw[16:20])
self.index_type_desc = {0: 'normal', 2:
'inflection', 6: 'calibre'}.get(self.index_type, 'unknown')
self.idxt_start, = struct.unpack('>I', raw[20:24])
self.index_count, = struct.unpack('>I', raw[24:28])
self.index_encoding_num, = struct.unpack('>I', raw[28:32])
self.index_encoding = {65001: 'utf-8', 1252:
'cp1252'}.get(self.index_encoding_num, 'unknown')
if self.index_encoding == 'unknown':
raise ValueError(
'Unknown index encoding: %d'%self.index_encoding_num)
self.unknown2 = raw[32:36]
self.num_index_entries, = struct.unpack('>I', raw[36:40])
self.ordt_start, = struct.unpack('>I', raw[40:44])
self.ligt_start, = struct.unpack('>I', raw[44:48])
self.num_of_ligt_entries, = struct.unpack('>I', raw[48:52])
self.num_of_cncx_blocks, = struct.unpack('>I', raw[52:56])
self.unknown3 = raw[56:180]
self.tagx_offset, = struct.unpack(b'>I', raw[180:184])
if self.tagx_offset != self.header_length:
raise ValueError('TAGX offset and header length disagree')
self.unknown4 = raw[184:self.header_length]
tagx = raw[self.header_length:]
if not tagx.startswith(b'TAGX'):
raise ValueError('Invalid TAGX section')
self.tagx_header_length, = struct.unpack('>I', tagx[4:8])
self.tagx_control_byte_count, = struct.unpack('>I', tagx[8:12])
tag_table = tagx[12:self.tagx_header_length]
if len(tag_table) % 4 != 0:
raise ValueError('Invalid Tag table')
num_tagx_entries = len(tag_table) // 4
self.tagx_entries = []
for i in range(num_tagx_entries):
self.tagx_entries.append(TagX(tag_table[i*4:(i+1)*4],
self.tagx_control_byte_count))
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
raise ValueError('TAGX last entry is not EOF')
idxt0_pos = self.header_length+self.tagx_header_length
num = ord(raw[idxt0_pos])
count_pos = idxt0_pos+1+num
self.last_entry = raw[idxt0_pos+1:count_pos]
self.ncx_count, = struct.unpack(b'>H', raw[count_pos:count_pos+2])
# There may be some alignment zero bytes between the end of the idxt0
# and self.idxt_start
idxt = raw[self.idxt_start:]
if idxt[:4] != b'IDXT':
raise ValueError('Invalid IDXT header')
length_check, = struct.unpack(b'>H', idxt[4:6])
if length_check != self.header_length + self.tagx_header_length:
raise ValueError('Length check failed')
if idxt[6:].replace(b'\0', b''):
raise ValueError('Non null trailing bytes after IDXT')
def __str__(self):
ans = ['*'*20 + ' Secondary Index Header '+ '*'*20]
a = ans.append
def u(w):
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
len(w), not bool(w.replace(b'\0', b'')) ))
a('Header length: %d'%self.header_length)
u(self.unknown1)
a('Index Type: %s (%d)'%(self.index_type_desc, self.index_type))
a('Offset to IDXT start: %d'%self.idxt_start)
a('Number of index records: %d'%self.index_count)
a('Index encoding: %s (%d)'%(self.index_encoding,
self.index_encoding_num))
u(self.unknown2)
a('Number of index entries: %d'% self.num_index_entries)
a('ORDT start: %d'%self.ordt_start)
a('LIGT start: %d'%self.ligt_start)
a('Number of LIGT entries: %d'%self.num_of_ligt_entries)
a('Number of cncx blocks: %d'%self.num_of_cncx_blocks)
u(self.unknown3)
a('TAGX offset: %d'%self.tagx_offset)
u(self.unknown4)
a('\n\n')
a('*'*20 + ' TAGX Header (%d bytes)'%self.tagx_header_length+ '*'*20)
a('Header length: %d'%self.tagx_header_length)
a('Control byte count: %d'%self.tagx_control_byte_count)
for i in self.tagx_entries:
a('\t' + repr(i))
a('Index of last IndexEntry in secondary index record: %s'% self.last_entry)
a('Number of entries in the NCX: %d'% self.ncx_count)
return '\n'.join(ans)
# }}}
class IndexHeader(object): # {{{
def __init__(self, record):
@ -455,12 +567,12 @@ class IndexHeader(object): # {{{
self.tagx_control_byte_count))
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
raise ValueError('TAGX last entry is not EOF')
self.tagx_entries = self.tagx_entries[:-1]
idxt0_pos = self.header_length+self.tagx_header_length
last_num, consumed = decode_hex_number(raw[idxt0_pos:])
count_pos = idxt0_pos + consumed
self.ncx_count, = struct.unpack(b'>H', raw[count_pos:count_pos+2])
self.last_entry = last_num
if last_num != self.ncx_count - 1:
raise ValueError('Last id number in the NCX != NCX count - 1')
@ -473,9 +585,12 @@ class IndexHeader(object): # {{{
length_check, = struct.unpack(b'>H', idxt[4:6])
if length_check != self.header_length + self.tagx_header_length:
raise ValueError('Length check failed')
if idxt[6:].replace(b'\0', b''):
raise ValueError('Non null trailing bytes after IDXT')
def __str__(self):
ans = ['*'*20 + ' Index Header '+ '*'*20]
ans = ['*'*20 + ' Index Header (%d bytes)'%len(self.record.raw)+ '*'*20]
a = ans.append
def u(w):
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
@ -503,6 +618,7 @@ class IndexHeader(object): # {{{
a('Control byte count: %d'%self.tagx_control_byte_count)
for i in self.tagx_entries:
a('\t' + repr(i))
a('Index of last IndexEntry in primary index record: %s'% self.last_entry)
a('Number of entries in the NCX: %d'% self.ncx_count)
return '\n'.join(ans)
@ -521,20 +637,30 @@ class Tag(object): # {{{
3: ('label_offset', 'Offset to label in CNCX'),
4: ('depth', 'Depth of this entry in TOC'),
11: ('secondary', '[unknown, unknown, '
'tag type from TAGX in primary index header]'),
# The remaining tag types have to be interpreted subject to the type
# of index entry they are present in
}
INTERPRET_MAP = {
'subchapter': {
5 : ('Parent chapter index', 'parent_index')
21 : ('Parent chapter index', 'parent_index')
},
'article' : {
5 : ('Class offset in cncx', 'class_offset'),
21 : ('Parent section index', 'parent_index'),
22 : ('Description offset in cncx', 'desc_offset'),
23 : ('Author offset in cncx', 'author_offset'),
69 : ('Offset from first image record num to the'
' image record associated with this article',
'image_index'),
70 : ('Description offset in cncx', 'desc_offset'),
71 : ('Image attribution offset in cncx',
'image_attr_offset'),
72 : ('Image caption offset in cncx',
'image_caption_offset'),
73 : ('Author offset in cncx', 'author_offset'),
},
'chapter_with_subchapters' : {
@ -546,6 +672,8 @@ class Tag(object): # {{{
5 : ('Class offset in cncx', 'class_offset'),
22 : ('First section index', 'first_child_index'),
23 : ('Last section index', 'last_child_index'),
69 : ('Offset from first image record num to masthead'
' record', 'image_index'),
},
'section' : {
@ -558,21 +686,25 @@ class Tag(object): # {{{
def __init__(self, tagx, vals, entry_type, cncx):
self.value = vals if len(vals) > 1 else vals[0]
self.value = vals if len(vals) > 1 else vals[0] if vals else None
self.entry_type = entry_type
tag_type = tagx.tag
self.cncx_value = None
if tagx.tag in self.TAG_MAP:
self.attr, self.desc = self.TAG_MAP[tagx.tag]
if tag_type in self.TAG_MAP:
self.attr, self.desc = self.TAG_MAP[tag_type]
else:
try:
td = self.INTERPRET_MAP[entry_type]
except:
raise ValueError('Unknown entry type: %s'%entry_type)
try:
self.desc, self.attr = td[tagx.tag]
self.desc, self.attr = td[tag_type]
except:
raise ValueError('Unknown tag: %d for entry type: %s'%(
tagx.tag, entry_type))
print ('Unknown tag value: %d'%tag_type)
self.desc = '??Unknown (tag value: %d type: %s)'%(
tag_type, entry_type)
self.attr = 'unknown'
if '_offset' in self.attr:
self.cncx_value = cncx[self.value]
@ -593,6 +725,9 @@ class IndexEntry(object): # {{{
'''
TYPES = {
# Present in secondary index record
0x01 : 'null',
0x02 : 'publication_meta',
# Present in book type files
0x0f : 'chapter',
0x6f : 'chapter_with_subchapters',
@ -603,7 +738,8 @@ class IndexEntry(object): # {{{
0x3f : 'article',
}
def __init__(self, ident, entry_type, raw, cncx, tagx_entries, flags=0):
def __init__(self, ident, entry_type, raw, cncx, tagx_entries,
control_byte_count):
self.index = ident
self.raw = raw
self.tags = []
@ -615,13 +751,29 @@ class IndexEntry(object): # {{{
try:
self.entry_type = self.TYPES[entry_type]
except KeyError:
raise ValueError('Unknown Index Entry type: %s'%hex(entry_type))
raise ValueError('Unknown Index Entry type: %s'%bin(entry_type))
if control_byte_count not in (1, 2):
raise ValueError('Unknown control byte count: %d'%
control_byte_count)
self.flags = 0
if control_byte_count == 2:
self.flags = ord(raw[0])
raw = raw[1:]
expected_tags = [tag for tag in tagx_entries if tag.bitmask &
entry_type]
flags = self.flags
for tag in expected_tags:
vals = []
if tag.tag > 64:
has_tag = flags & 0b1
flags = flags >> 1
if not has_tag: continue
for i in range(tag.num_values):
if not raw:
raise ValueError('Index entry does not match TAGX header')
@ -630,26 +782,11 @@ class IndexEntry(object): # {{{
vals.append(val)
self.tags.append(Tag(tag, vals, self.entry_type, cncx))
if flags & 0b10:
# Look for optional description and author
desc_tag = [t for t in tagx_entries if t.tag == 22]
if desc_tag and raw:
val, consumed = decint(raw)
raw = raw[consumed:]
if val:
self.tags.append(Tag(desc_tag[0], [val], self.entry_type,
cncx))
if flags & 0b100:
aut_tag = [t for t in tagx_entries if t.tag == 23]
if aut_tag and raw:
val, consumed = decint(raw)
raw = raw[consumed:]
if val:
self.tags.append(Tag(aut_tag[0], [val], self.entry_type,
cncx))
self.consumed = len(orig_raw) - len(raw)
self.trailing_bytes = raw
if self.trailing_bytes.replace(b'\0', b''):
raise ValueError('%s has leftover bytes: %s'%(self, format_bytes(
self.trailing_bytes)))
@property
def label(self):
@ -701,10 +838,12 @@ class IndexEntry(object): # {{{
return -1
def __str__(self):
ans = ['Index Entry(index=%s, entry_type=%s (%s), length=%d, byte_size=%d)'%(
self.index, self.entry_type, bin(self.entry_type_raw)[2:],
ans = ['Index Entry(index=%s, entry_type=%s, flags=%s, '
'length=%d, byte_size=%d)'%(
self.index, self.entry_type, bin(self.flags)[2:],
len(self.tags), self.byte_size)]
for tag in self.tags:
if tag.value is not None:
ans.append('\t'+str(tag))
if self.first_child_index != -1:
ans.append('\tNumber of children: %d'%(self.last_child_index -
@ -715,6 +854,90 @@ class IndexEntry(object): # {{{
# }}}
class SecondaryIndexRecord(object): # {{{
def __init__(self, record, index_header, cncx):
self.record = record
raw = self.record.raw
if raw[:4] != b'INDX':
raise ValueError('Invalid Primary Index Record')
u = struct.unpack
self.header_length, = u('>I', raw[4:8])
self.unknown1 = raw[8:12]
self.header_type, = u('>I', raw[12:16])
self.unknown2 = raw[16:20]
self.idxt_offset, self.idxt_count = u(b'>II', raw[20:28])
if self.idxt_offset < 192:
raise ValueError('Unknown Index record structure')
self.unknown3 = raw[28:36]
self.unknown4 = raw[36:192] # Should be 156 bytes
self.index_offsets = []
indices = raw[self.idxt_offset:]
if indices[:4] != b'IDXT':
raise ValueError("Invalid IDXT index table")
indices = indices[4:]
for i in range(self.idxt_count):
off, = u(b'>H', indices[i*2:(i+1)*2])
self.index_offsets.append(off-192)
rest = indices[(i+1)*2:]
if rest.replace(b'\0', ''): # There can be padding null bytes
raise ValueError('Extra bytes after IDXT table: %r'%rest)
indxt = raw[192:self.idxt_offset]
self.size_of_indxt_block = len(indxt)
self.indices = []
for i, off in enumerate(self.index_offsets):
try:
next_off = self.index_offsets[i+1]
except:
next_off = len(indxt)
num = ord(indxt[off])
index = indxt[off+1:off+1+num]
consumed = 1 + num
entry_type = ord(indxt[off+consumed])
pos = off+consumed+1
idxe = IndexEntry(index, entry_type,
indxt[pos:next_off], cncx,
index_header.tagx_entries,
index_header.tagx_control_byte_count)
self.indices.append(idxe)
rest = indxt[pos+self.indices[-1].consumed:]
if rest.replace(b'\0', b''): # There can be padding null bytes
raise ValueError('Extra bytes after IDXT table: %r'%rest)
def __str__(self):
ans = ['*'*20 + ' Secondary Index Record (%d bytes) '%len(self.record.raw)+ '*'*20]
a = ans.append
def u(w):
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
len(w), not bool(w.replace(b'\0', b'')) ))
a('Header length: %d'%self.header_length)
u(self.unknown1)
a('Unknown (header type? index record number? always 1?): %d'%self.header_type)
u(self.unknown2)
a('IDXT Offset (%d block size): %d'%(self.size_of_indxt_block,
self.idxt_offset))
a('IDXT Count: %d'%self.idxt_count)
u(self.unknown3)
u(self.unknown4)
a('Index offsets: %r'%self.index_offsets)
a('\nIndex Entries (%d entries):'%len(self.indices))
for entry in self.indices:
a(str(entry))
a('')
return '\n'.join(ans)
# }}}
class IndexRecord(object): # {{{
'''
@ -724,6 +947,7 @@ class IndexRecord(object): # {{{
def __init__(self, record, index_header, cncx):
self.record = record
self.alltext = None
raw = self.record.raw
if raw[:4] != b'INDX':
@ -763,17 +987,15 @@ class IndexRecord(object): # {{{
next_off = len(indxt)
index, consumed = decode_hex_number(indxt[off:])
entry_type = ord(indxt[off+consumed])
d, flags = 1, 0
if index_header.index_type == 6:
flags = ord(indxt[off+consumed+d])
d += 1
pos = off+consumed+d
self.indices.append(IndexEntry(index, entry_type,
pos = off+consumed+1
idxe = IndexEntry(index, entry_type,
indxt[pos:next_off], cncx,
index_header.tagx_entries, flags=flags))
index_header.tagx_entries,
index_header.tagx_control_byte_count)
self.indices.append(idxe)
rest = indxt[pos+self.indices[-1].consumed:]
if rest.replace(b'\0', ''): # There can be padding null bytes
if rest.replace(b'\0', b''): # There can be padding null bytes
raise ValueError('Extra bytes after IDXT table: %r'%rest)
def get_parent(self, index):
@ -803,13 +1025,23 @@ class IndexRecord(object): # {{{
a('Index offsets: %r'%self.index_offsets)
a('\nIndex Entries (%d entries):'%len(self.indices))
for entry in self.indices:
a(str(entry)+'\n')
offset = entry.offset
a(str(entry))
t = self.alltext
if offset is not None and self.alltext is not None:
a('\tHTML before offset: %r'%t[offset-50:offset])
a('\tHTML after offset: %r'%t[offset:offset+50])
p = offset+entry.size
a('\tHTML before end: %r'%t[p-50:p])
a('\tHTML after end: %r'%t[p:p+50])
a('')
return '\n'.join(ans)
# }}}
class CNCX(object) : # {{{
class CNCX(object): # {{{
'''
Parses the records that contain the compiled NCX (all strings from the
@ -826,8 +1058,16 @@ class CNCX(object) : # {{{
while pos < len(raw):
length, consumed = decint(raw[pos:])
if length > 0:
try:
self.records[pos+record_offset] = raw[
pos+consumed:pos+consumed+length].decode(codec)
except:
byts = raw[pos:]
r = format_bytes(byts)
print ('CNCX entry at offset %d has unknown format %s'%(
pos+record_offset, r))
self.records[pos+record_offset] = r
pos = len(raw)
pos += consumed+length
record_offset += 0x10000
@ -849,6 +1089,7 @@ class TextRecord(object): # {{{
self.trailing_data, self.raw = get_trailing_data(record.raw, extra_data_flags)
raw_trailing_bytes = record.raw[len(self.raw):]
self.raw = decompress(self.raw)
if 0 in self.trailing_data:
self.trailing_data['multibyte_overlap'] = self.trailing_data.pop(0)
if 1 in self.trailing_data:
@ -857,6 +1098,11 @@ class TextRecord(object): # {{{
self.trailing_data['uncrossable_breaks'] = self.trailing_data.pop(2)
self.trailing_data['raw_bytes'] = raw_trailing_bytes
for typ, val in self.trailing_data.iteritems():
if isinstance(typ, int):
print ('Record %d has unknown trailing data of type: %d : %r'%
(idx, typ, val))
self.idx = idx
def dump(self, folder):
@ -890,7 +1136,7 @@ class BinaryRecord(object): # {{{
self.raw = record.raw
sig = self.raw[:4]
name = '%06d'%idx
if sig in (b'FCIS', b'FLIS', b'SRCS'):
if sig in (b'FCIS', b'FLIS', b'SRCS', b'DATP'):
name += '-' + sig.decode('ascii')
elif sig == b'\xe9\x8e\r\n':
name += '-' + 'EOF'
@ -960,8 +1206,7 @@ class TBSIndexing(object): # {{{
'(%d ends, %d complete, %d starts)')%tuple(map(len, (s+e+c, e,
c, s))))
byts = bytearray(r.trailing_data.get('indexing', b''))
sbyts = tuple(hex(b)[2:] for b in byts)
ans.append('TBS bytes: %s'%(' '.join(sbyts)))
ans.append('TBS bytes: %s'%format_bytes(byts))
for typ, entries in (('Ends', e), ('Complete', c), ('Starts', s)):
if entries:
ans.append('\t%s:'%typ)
@ -979,8 +1224,7 @@ class TBSIndexing(object): # {{{
tbs_type = 0
is_periodical = self.doc_type in (257, 258, 259)
if len(byts):
outermost_index, extra, consumed = decode_tbs(byts, flag_size=4 if
is_periodical else 3)
outermost_index, extra, consumed = decode_tbs(byts, flag_size=3)
byts = byts[consumed:]
for k in extra:
tbs_type |= k
@ -988,8 +1232,14 @@ class TBSIndexing(object): # {{{
ans.append('Outermost index: %d'%outermost_index)
ans.append('Unknown extra start bytes: %s'%repr_extra(extra))
if is_periodical: # Hierarchical periodical
try:
byts, a = self.interpret_periodical(tbs_type, byts,
dat['geom'][0])
except:
import traceback
traceback.print_exc()
a = []
print ('Failed to decode TBS bytes for record: %d'%r.idx)
ans += a
if byts:
sbyts = tuple(hex(b)[2:] for b in byts)
@ -1112,15 +1362,16 @@ class MOBIFile(object): # {{{
self.records.append(Record(section(i), self.record_headers[i]))
self.mobi_header = MOBIHeader(self.records[0])
self.huffman_record_nums = []
if 'huff' in self.mobi_header.compression.lower():
huffrecs = [r.raw for r in
xrange(self.mobi_header.huffman_record_offset,
self.huffman_record_nums = list(xrange(self.mobi_header.huffman_record_offset,
self.mobi_header.huffman_record_offset +
self.mobi_header.huffman_record_count)]
self.mobi_header.huffman_record_count))
huffrecs = [self.records[r].raw for r in self.huffman_record_nums]
from calibre.ebooks.mobi.huffcdic import HuffReader
huffs = HuffReader(huffrecs)
decompress = huffs.decompress
decompress = huffs.unpack
elif 'palmdoc' in self.mobi_header.compression.lower():
from calibre.ebooks.compression.palmdoc import decompress_doc
decompress = decompress_doc
@ -1139,6 +1390,14 @@ class MOBIFile(object): # {{{
self.index_header, self.cncx)
self.indexing_record_nums = set(xrange(pir,
pir+2+self.index_header.num_of_cncx_blocks))
self.secondary_index_record = self.secondary_index_header = None
sir = self.mobi_header.secondary_index_record
if sir != 0xffffffff:
self.secondary_index_header = SecondaryIndexHeader(self.records[sir])
self.indexing_record_nums.add(sir)
self.secondary_index_record = SecondaryIndexRecord(
self.records[sir+1], self.secondary_index_header, self.cncx)
self.indexing_record_nums.add(sir+1)
ntr = self.mobi_header.number_of_text_records
@ -1151,7 +1410,7 @@ class MOBIFile(object): # {{{
min(len(self.records), ntr+1))]
self.image_records, self.binary_records = [], []
for i in xrange(fntbr, len(self.records)):
if i in self.indexing_record_nums:
if i in self.indexing_record_nums or i in self.huffman_record_nums:
continue
r = self.records[i]
fmt = None
@ -1181,22 +1440,44 @@ class MOBIFile(object): # {{{
print (str(self.mobi_header).encode('utf-8'), file=f)
# }}}
def inspect_mobi(path_or_stream, prefix='decompiled'):
def inspect_mobi(path_or_stream, ddir=None): # {{{
stream = (path_or_stream if hasattr(path_or_stream, 'read') else
open(path_or_stream, 'rb'))
f = MOBIFile(stream)
ddir = prefix + '_' + os.path.splitext(os.path.basename(stream.name))[0]
if ddir is None:
ddir = 'decompiled_' + os.path.splitext(os.path.basename(stream.name))[0]
try:
shutil.rmtree(ddir)
except:
pass
os.mkdir(ddir)
os.makedirs(ddir)
with open(os.path.join(ddir, 'header.txt'), 'wb') as out:
f.print_header(f=out)
alltext = os.path.join(ddir, 'text.html')
with open(alltext, 'wb') as of:
alltext = b''
for rec in f.text_records:
of.write(rec.raw)
alltext += rec.raw
of.seek(0)
root = html.fromstring(alltext.decode('utf-8'))
with open(os.path.join(ddir, 'pretty.html'), 'wb') as of:
of.write(html.tostring(root, pretty_print=True, encoding='utf-8',
include_meta_content_type=True))
if f.index_header is not None:
f.index_record.alltext = alltext
with open(os.path.join(ddir, 'index.txt'), 'wb') as out:
print(str(f.index_header), file=out)
print('\n\n', file=out)
if f.secondary_index_header is not None:
print(str(f.secondary_index_header).encode('utf-8'), file=out)
print('\n\n', file=out)
if f.secondary_index_record is not None:
print(str(f.secondary_index_record).encode('utf-8'), file=out)
print('\n\n', file=out)
print(str(f.cncx).encode('utf-8'), file=out)
print('\n\n', file=out)
print(str(f.index_record), file=out)
@ -1211,21 +1492,11 @@ def inspect_mobi(path_or_stream, prefix='decompiled'):
for rec in getattr(f, attr):
rec.dump(tdir)
alltext = os.path.join(ddir, 'text.html')
with open(alltext, 'wb') as of:
alltext = b''
for rec in f.text_records:
of.write(rec.raw)
alltext += rec.raw
of.seek(0)
root = html.fromstring(alltext.decode('utf-8'))
with open(os.path.join(ddir, 'pretty.html'), 'wb') as of:
of.write(html.tostring(root, pretty_print=True, encoding='utf-8',
include_meta_content_type=True))
print ('Debug data saved to:', ddir)
# }}}
def main():
inspect_mobi(sys.argv[1])

View File

@ -1,8 +1,12 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
'''
Decompress MOBI files compressed with the Huff/cdic algorithm. Code thanks to darkninja
and igorsk.
@ -12,82 +16,92 @@ import struct
from calibre.ebooks.mobi import MobiError
class BitReader(object):
class Reader(object):
def __init__(self, data):
self.data, self.pos, self.nbits = data + "\x00\x00\x00\x00", 0, len(data) * 8
def __init__(self):
self.q = struct.Struct(b'>Q').unpack_from
def peek(self, n):
r, g = 0, 0
while g < n:
r, g = (r << 8) | ord(self.data[(self.pos+g)>>3]), g + 8 - ((self.pos+g) & 7)
return (r >> (g - n)) & ((1 << n) - 1)
def load_huff(self, huff):
if huff[0:8] != b'HUFF\x00\x00\x00\x18':
raise MobiError('Invalid HUFF header')
off1, off2 = struct.unpack_from(b'>LL', huff, 8)
def eat(self, n):
self.pos += n
return self.pos <= self.nbits
def dict1_unpack(v):
codelen, term, maxcode = v&0x1f, v&0x80, v>>8
assert codelen != 0
if codelen <= 8:
assert term
maxcode = ((maxcode + 1) << (32 - codelen)) - 1
return (codelen, term, maxcode)
self.dict1 = map(dict1_unpack, struct.unpack_from(b'>256L', huff, off1))
def left(self):
return self.nbits - self.pos
dict2 = struct.unpack_from(b'>64L', huff, off2)
self.mincode, self.maxcode = (), ()
for codelen, mincode in enumerate((0,) + dict2[0::2]):
self.mincode += (mincode << (32 - codelen), )
for codelen, maxcode in enumerate((0,) + dict2[1::2]):
self.maxcode += (((maxcode + 1) << (32 - codelen)) - 1, )
self.dictionary = []
def load_cdic(self, cdic):
if cdic[0:8] != b'CDIC\x00\x00\x00\x10':
raise MobiError('Invalid CDIC header')
phrases, bits = struct.unpack_from(b'>LL', cdic, 8)
n = min(1<<bits, phrases-len(self.dictionary))
h = struct.Struct(b'>H').unpack_from
def getslice(off):
blen, = h(cdic, 16+off)
slice = cdic[18+off:18+off+(blen&0x7fff)]
return (slice, blen&0x8000)
self.dictionary += map(getslice, struct.unpack_from(b'>%dH' % n, cdic, 16))
def unpack(self, data):
q = self.q
bitsleft = len(data) * 8
data += b'\x00\x00\x00\x00\x00\x00\x00\x00'
pos = 0
x, = q(data, pos)
n = 32
s = []
while True:
if n <= 0:
pos += 4
x, = q(data, pos)
n += 32
code = (x >> n) & ((1 << 32) - 1)
codelen, term, maxcode = self.dict1[code >> 24]
if not term:
while code < self.mincode[codelen]:
codelen += 1
maxcode = self.maxcode[codelen]
n -= codelen
bitsleft -= codelen
if bitsleft < 0:
break
r = (maxcode - code) >> (32 - codelen)
slice_, flag = self.dictionary[r]
if not flag:
self.dictionary[r] = None
slice_ = self.unpack(slice_)
self.dictionary[r] = (slice_, 1)
s.append(slice_)
return b''.join(s)
class HuffReader(object):
def __init__(self, huffs):
self.huffs = huffs
self.reader = Reader()
self.reader.load_huff(huffs[0])
for cdic in huffs[1:]:
self.reader.load_cdic(cdic)
if huffs[0][0:4] != 'HUFF' or huffs[0][4:8] != '\x00\x00\x00\x18':
raise MobiError('Invalid HUFF header')
def unpack(self, section):
return self.reader.unpack(section)
if huffs[1][0:4] != 'CDIC' or huffs[1][4:8] != '\x00\x00\x00\x10':
raise ValueError('Invalid CDIC header')
self.entry_bits, = struct.unpack('>L', huffs[1][12:16])
off1,off2 = struct.unpack('>LL', huffs[0][16:24])
self.dict1 = struct.unpack('<256L', huffs[0][off1:off1+256*4])
self.dict2 = struct.unpack('<64L', huffs[0][off2:off2+64*4])
self.dicts = huffs[1:]
self.r = ''
def _unpack(self, bits, depth = 0):
if depth > 32:
raise MobiError('Corrupt file')
while bits.left():
dw = bits.peek(32)
v = self.dict1[dw >> 24]
codelen = v & 0x1F
assert codelen != 0
code = dw >> (32 - codelen)
r = (v >> 8)
if not (v & 0x80):
while code < self.dict2[(codelen-1)*2]:
codelen += 1
code = dw >> (32 - codelen)
r = self.dict2[(codelen-1)*2+1]
r -= code
assert codelen != 0
if not bits.eat(codelen):
return
dicno = r >> self.entry_bits
off1 = 16 + (r - (dicno << self.entry_bits)) * 2
dic = self.dicts[dicno]
off2 = 16 + ord(dic[off1]) * 256 + ord(dic[off1+1])
blen = ord(dic[off2]) * 256 + ord(dic[off2+1])
slice = dic[off2+2:off2+2+(blen&0x7fff)]
if blen & 0x8000:
self.r += slice
else:
self._unpack(BitReader(slice), depth + 1)
def unpack(self, data):
self.r = ''
self._unpack(BitReader(data))
return self.r
def decompress(self, sections):
r = ''
for data in sections:
r += self.unpack(data)
if r.endswith('#'):
r = r[:-1]
return r

View File

@ -4,6 +4,7 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
from struct import pack
from calibre.utils.localization import lang_as_iso639_1
lang_codes = {
}
@ -314,7 +315,8 @@ def iana2mobi(icode):
subtags = list(icode.split('-'))
while len(subtags) > 0:
lang = subtags.pop(0).lower()
if lang in IANA_MOBI:
lang = lang_as_iso639_1(lang)
if lang and lang in IANA_MOBI:
langdict = IANA_MOBI[lang]
break

View File

@ -532,7 +532,7 @@ class MobiMLizer(object):
bstate.pbreak = True
if isblock:
para = bstate.para
if para is not None and para.text == u'\xa0':
if para is not None and para.text == u'\xa0' and len(para) < 1:
para.getparent().replace(para, etree.Element(XHTML('br')))
bstate.para = None
bstate.istate = None

View File

@ -50,13 +50,11 @@ class MOBIOutput(OutputFormatPlugin):
help=_('When adding the Table of Contents to the book, add it at the start of the '
'book instead of the end. Not recommended.')
),
OptionRecommendation(name='mobi_navpoints_only_deepest',
recommended_value=False,
help=_('When adding navpoints for the chapter-to-chapter'
' navigation on the kindle, use only the lowest level '
'of items in the TOC, instead of items at every level.')
OptionRecommendation(name='extract_to', recommended_value=None,
help=_('Extract the contents of the MOBI file to the'
' specified directory. If the directory already '
'exists, it will be deleted.')
),
OptionRecommendation(name='kindlegen',
recommended_value=False,
help=('Use kindlegen (must be in your PATH) to generate the'
@ -185,3 +183,8 @@ class MOBIOutput(OutputFormatPlugin):
write_page_breaks_after_item=write_page_breaks_after_item)
writer(oeb, output_path)
if opts.extract_to is not None:
from calibre.ebooks.mobi.debug import inspect_mobi
ddir = opts.extract_to
inspect_mobi(output_path, ddir=ddir)

View File

@ -859,16 +859,19 @@ class MobiReader(object):
processed_records += list(range(self.book_header.huff_offset,
self.book_header.huff_offset + self.book_header.huff_number))
huff = HuffReader(huffs)
self.mobi_html = huff.decompress(text_sections)
unpack = huff.unpack
elif self.book_header.compression_type == '\x00\x02':
for section in text_sections:
self.mobi_html += decompress_doc(section)
unpack = decompress_doc
elif self.book_header.compression_type == '\x00\x01':
self.mobi_html = ''.join(text_sections)
unpack = lambda x: x
else:
raise MobiError('Unknown compression algorithm: %s' % repr(self.book_header.compression_type))
self.mobi_html = b''.join(map(unpack, text_sections))
if self.mobi_html.endswith(b'#'):
self.mobi_html = self.mobi_html[:-1]
if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower():
self.mobi_html = self.mobi_html.replace('\r ', '\n\n ')
self.mobi_html = self.mobi_html.replace('\0', '')

View File

@ -13,9 +13,9 @@ First sequences:
0b0110 : 80 2
0b0111 : 80 2 80
Other sequences:
0b0101 : 4 1a
0b0001 : c b1
0b0001 = 0
0b0010 = 0
0b0100 = 2
Opening record
----------------

View File

@ -169,18 +169,25 @@ def get_trailing_data(record, extra_data_flags):
:return: Trailing data, record - trailing data
'''
data = OrderedDict()
for i in xrange(16, -1, -1):
flag = 1 << i # 2**i
if flag & extra_data_flags:
if i == 0:
flags = extra_data_flags >> 1
num = 0
while flags:
num += 1
if flags & 0b1:
sz, consumed = decint(record, forward=False)
if sz > consumed:
data[num] = record[-sz:-consumed]
record = record[:-sz]
flags >>= 1
# Read multibyte chars if any
if extra_data_flags & 0b1:
# Only the first two bits are used for the size since there can
# never be more than 3 trailing multibyte chars
sz = (ord(record[-1]) & 0b11) + 1
consumed = 1
else:
sz, consumed = decint(record, forward=False)
if sz > consumed:
data[i] = record[-sz:-consumed]
data[0] = record[-sz:-consumed]
record = record[:-sz]
return data, record

View File

@ -430,6 +430,7 @@ class MobiWriter(object):
text.seek(npos)
return data, overlap
# TBS {{{
def _generate_flat_indexed_navpoints(self):
# Assemble a HTMLRecordData instance for each HTML record
# Return True if valid, False if invalid
@ -1174,6 +1175,8 @@ class MobiWriter(object):
self._tbSequence = tbSequence
# }}}
def _evaluate_periodical_toc(self):
'''
Periodical:
@ -1634,7 +1637,7 @@ class MobiWriter(object):
now = int(time.time())
nrecords = len(self._records)
self._write(title, pack('>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
'BOOK', 'MOBI', pack('>IIH', nrecords, 0, nrecords))
'BOOK', 'MOBI', pack('>IIH', (2*nrecords)-1, 0, nrecords))
offset = self._tell() + (8 * nrecords) + 2
for i, record in enumerate(self._records):
self._write(pack('>I', offset), '\0', pack('>I', 2*i)[1:])
@ -1707,8 +1710,6 @@ class MobiWriter(object):
'''
from calibre.ebooks.oeb.base import TOC
items = list(self._oeb.toc.iterdescendants())
if self.opts.mobi_navpoints_only_deepest:
items = [i for i in items if i.depth == 1]
offsets = {i:self._id_offsets.get(i.href, -1) for i in items if i.href}
items = [i for i in items if offsets[i] > -1]
items.sort(key=lambda i:offsets[i])

View File

@ -2,7 +2,7 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
from future_builtins import filter
from future_builtins import filter, map
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
@ -14,8 +14,7 @@ from collections import OrderedDict, defaultdict
from calibre.ebooks.mobi.writer2 import RECORD_SIZE
from calibre.ebooks.mobi.utils import (encint, encode_number_as_hex,
encode_tbs, align_block, utf8_text, detect_periodical)
encode_tbs, align_block, utf8_text)
class CNCX(object): # {{{
@ -34,6 +33,10 @@ class CNCX(object): # {{{
self.strings[item.title] = 0
if is_periodical:
self.strings[item.klass] = 0
if item.author:
self.strings[item.author] = 0
if item.description:
self.strings[item.description] = 0
self.records = []
offset = 0
@ -61,7 +64,79 @@ class CNCX(object): # {{{
return self.strings[string]
# }}}
class IndexEntry(object): # {{{
class TAGX(object): # {{{
BITMASKS = {11:0b1}
BITMASKS.update({x:(1 << i) for i, x in enumerate([1, 2, 3, 4, 5, 21, 22, 23])})
BITMASKS.update({x:(1 << i) for i, x in enumerate([69, 70, 71, 72, 73])})
NUM_VALUES = defaultdict(lambda :1)
NUM_VALUES[11] = 3
NUM_VALUES[0] = 0
def __init__(self):
self.byts = bytearray()
def add_tag(self, tag):
buf = self.byts
buf.append(tag)
buf.append(self.NUM_VALUES[tag])
# bitmask
buf.append(self.BITMASKS[tag] if tag else 0)
# eof
buf.append(0 if tag else 1)
def header(self, control_byte_count):
header = b'TAGX'
# table length, control byte count
header += pack(b'>II', 12+len(self.byts), control_byte_count)
return header
@property
def periodical(self):
'''
TAGX block for the Primary index header of a periodical
'''
list(map(self.add_tag, (1, 2, 3, 4, 5, 21, 22, 23, 0, 69, 70, 71, 72,
73, 0)))
return self.header(2) + bytes(self.byts)
@property
def secondary(self):
'''
TAGX block for the secondary index header of a periodical
'''
list(map(self.add_tag, (11, 0)))
return self.header(1) + bytes(self.byts)
class TAGX_BOOK(TAGX):
BITMASKS = dict(TAGX.BITMASKS)
BITMASKS.update({x:(1 << i) for i, x in enumerate([1, 2, 3, 4, 21, 22, 23])})
@property
def hierarchical_book(self):
'''
TAGX block for the primary index header of a hierarchical book
'''
list(map(self.add_tag, (1, 2, 3, 4, 21, 22, 23, 0)))
return self.header(1) + bytes(self.byts)
@property
def flat_book(self):
'''
TAGX block for the primary index header of a flat book
'''
list(map(self.add_tag, (1, 2, 3, 4, 0)))
return self.header(1) + bytes(self.byts)
# }}}
# Index Entries {{{
class IndexEntry(object):
TAG_VALUES = {
'offset': 1,
@ -69,17 +144,21 @@ class IndexEntry(object): # {{{
'label_offset': 3,
'depth': 4,
'class_offset': 5,
'secondary': 11,
'parent_index': 21,
'first_child_index': 22,
'last_child_index': 23,
'image_index': 69,
'desc_offset': 70,
'author_offset': 73,
}
RTAG_MAP = {v:k for k, v in TAG_VALUES.iteritems()}
BITMASKS = [1, 2, 3, 4, 5, 21, 22, 23,]
def __init__(self, offset, label_offset, depth=0, class_offset=None):
def __init__(self, offset, label_offset):
self.offset, self.label_offset = offset, label_offset
self.depth, self.class_offset = depth, class_offset
self.depth, self.class_offset = 0, None
self.control_byte_count = 1
self.length = 0
self.index = 0
@ -88,6 +167,10 @@ class IndexEntry(object): # {{{
self.first_child_index = None
self.last_child_index = None
self.image_index = None
self.author_offset = None
self.desc_offset = None
def __repr__(self):
return ('IndexEntry(offset=%r, depth=%r, length=%r, index=%r,'
' parent_index=%r)')%(self.offset, self.depth, self.length,
@ -99,35 +182,6 @@ class IndexEntry(object): # {{{
def fset(self, val): self.length = val
return property(fget=fget, fset=fset, doc='Alias for length')
@classmethod
def tagx_block(cls, for_periodical=True):
buf = bytearray()
def add_tag(tag, num_values=1):
buf.append(tag)
buf.append(num_values)
# bitmask
buf.append(1 << (cls.BITMASKS.index(tag)))
# eof
buf.append(0)
for tag in xrange(1, 5):
add_tag(tag)
if for_periodical:
for tag in (5, 21, 22, 23):
add_tag(tag)
# End of TAGX record
for i in xrange(3): buf.append(0)
buf.append(1)
header = b'TAGX'
header += pack(b'>I', 12+len(buf)) # table length
header += pack(b'>I', 1) # control byte count
return header + bytes(buf)
@property
def next_offset(self):
return self.offset + self.length
@ -145,24 +199,100 @@ class IndexEntry(object): # {{{
def entry_type(self):
ans = 0
for tag in self.tag_nums:
ans |= (1 << self.BITMASKS.index(tag)) # 1 << x == 2**x
ans |= TAGX.BITMASKS[tag]
return ans
def attr_for_tag(self, tag):
return self.RTAG_MAP[tag]
@property
def bytestring(self):
buf = StringIO()
if isinstance(self.index, int):
buf.write(encode_number_as_hex(self.index))
else:
raw = bytearray(self.index.encode('ascii'))
raw.insert(0, len(raw))
buf.write(bytes(raw))
et = self.entry_type
buf.write(bytes(bytearray([et])))
for tag in self.tag_nums:
attr = self.RTAG_MAP[tag]
if self.control_byte_count == 2:
flags = 0
for attr in ('image_index', 'desc_offset', 'author_offset'):
val = getattr(self, attr)
if val is not None:
tag = self.TAG_VALUES[attr]
bm = TAGX.BITMASKS[tag]
flags |= bm
buf.write(bytes(bytearray([flags])))
for tag in self.tag_nums:
attr = self.attr_for_tag(tag)
val = getattr(self, attr)
if isinstance(val, int):
val = [val]
for x in val:
buf.write(encint(x))
if self.control_byte_count == 2:
for attr in ('image_index', 'desc_offset', 'author_offset'):
val = getattr(self, attr)
if val is not None:
buf.write(encint(val))
ans = buf.getvalue()
return ans
class BookIndexEntry(IndexEntry):
@property
def entry_type(self):
tagx = TAGX_BOOK()
ans = 0
for tag in self.tag_nums:
ans |= tagx.BITMASKS[tag]
return ans
class PeriodicalIndexEntry(IndexEntry):
def __init__(self, offset, label_offset, class_offset, depth):
IndexEntry.__init__(self, offset, label_offset)
self.depth = depth
self.class_offset = class_offset
self.control_byte_count = 2
class SecondaryIndexEntry(IndexEntry):
INDEX_MAP = {'author':73, 'caption':72, 'credit':71, 'description':70,
'mastheadImage':69}
def __init__(self, index):
IndexEntry.__init__(self, 0, 0)
self.index = index
tag = self.INDEX_MAP[index]
# The values for this index entry
# I dont know what the 5 means, it is not the number of entries
self.secondary = [5 if tag == min(
self.INDEX_MAP.itervalues()) else 0, 0, tag]
@property
def tag_nums(self):
yield 11
@property
def entry_type(self):
return 1
@classmethod
def entries(cls):
rmap = {v:k for k,v in cls.INDEX_MAP.iteritems()}
for tag in sorted(rmap, reverse=True):
yield cls(rmap[tag])
# }}}
class TBS(object): # {{{
@ -323,16 +453,23 @@ class TBS(object): # {{{
class Indexer(object): # {{{
def __init__(self, serializer, number_of_text_records,
size_of_last_text_record, opts, oeb):
size_of_last_text_record, masthead_offset, is_periodical,
opts, oeb):
self.serializer = serializer
self.number_of_text_records = number_of_text_records
self.text_size = (RECORD_SIZE * (self.number_of_text_records-1) +
size_of_last_text_record)
self.masthead_offset = masthead_offset
self.secondary_record_offset = None
self.oeb = oeb
self.log = oeb.log
self.opts = opts
self.is_periodical = detect_periodical(self.oeb.toc, self.log)
self.is_periodical = is_periodical
if self.is_periodical and self.masthead_offset is None:
raise ValueError('Periodicals must have a masthead')
self.log('Generating MOBI index for a %s'%('periodical' if
self.is_periodical else 'book'))
self.is_flat_periodical = False
@ -343,6 +480,16 @@ class Indexer(object): # {{{
self.records = []
if self.is_periodical:
# Ensure all articles have an author and description before
# creating the CNCX
for node in oeb.toc.iterdescendants():
if node.klass == 'article':
aut, desc = node.author, node.description
if not aut: aut = _('Unknown')
if not desc: desc = _('No details available')
node.author, node.description = aut, desc
self.cncx = CNCX(oeb.toc, self.is_periodical)
if self.is_periodical:
@ -354,12 +501,17 @@ class Indexer(object): # {{{
self.records.insert(0, self.create_header())
self.records.extend(self.cncx.records)
if is_periodical:
self.secondary_record_offset = len(self.records)
self.records.append(self.create_header(secondary=True))
self.records.append(self.create_index_record(secondary=True))
self.calculate_trailing_byte_sequences()
def create_index_record(self): # {{{
def create_index_record(self, secondary=False): # {{{
header_length = 192
buf = StringIO()
indices = self.indices
indices = list(SecondaryIndexEntry.entries()) if secondary else self.indices
# Write index entries
offsets = []
@ -399,9 +551,15 @@ class Indexer(object): # {{{
return ans
# }}}
def create_header(self): # {{{
def create_header(self, secondary=False): # {{{
buf = StringIO()
tagx_block = IndexEntry.tagx_block(self.is_periodical)
if secondary:
tagx_block = TAGX().secondary
else:
tagx_block = (TAGX().periodical if self.is_periodical else
(TAGX_BOOK().hierarchical_book if
self.book_has_subchapters else
TAGX_BOOK().flat_book))
header_length = 192
# Ident 0 - 4
@ -420,7 +578,7 @@ class Indexer(object): # {{{
buf.write(pack(b'>I', 0)) # Filled in later
# Number of index records 24-28
buf.write(pack(b'>I', len(self.records)))
buf.write(pack(b'>I', 1 if secondary else len(self.records)))
# Index Encoding 28-32
buf.write(pack(b'>I', 65001)) # utf-8
@ -429,7 +587,8 @@ class Indexer(object): # {{{
buf.write(b'\xff'*4)
# Number of index entries 36-40
buf.write(pack(b'>I', len(self.indices)))
indices = list(SecondaryIndexEntry.entries()) if secondary else self.indices
buf.write(pack(b'>I', len(indices)))
# ORDT offset 40-44
buf.write(pack(b'>I', 0))
@ -441,7 +600,7 @@ class Indexer(object): # {{{
buf.write(pack(b'>I', 0))
# Number of CNCX records 52-56
buf.write(pack(b'>I', len(self.cncx.records)))
buf.write(pack(b'>I', 0 if secondary else len(self.cncx.records)))
# Unknown 56-180
buf.write(b'\0'*124)
@ -455,10 +614,16 @@ class Indexer(object): # {{{
# TAGX block
buf.write(tagx_block)
num = len(self.indices)
num = len(indices)
# The index of the last entry in the NCX
buf.write(encode_number_as_hex(num-1))
idx = indices[-1].index
if isinstance(idx, int):
idx = encode_number_as_hex(idx)
else:
idx = idx.encode('ascii')
idx = (bytes(bytearray([len(idx)]))) + idx
buf.write(idx)
# The number of entries in the NCX
buf.write(pack(b'>H', num))
@ -480,26 +645,52 @@ class Indexer(object): # {{{
# }}}
def create_book_index(self): # {{{
self.book_has_subchapters = False
indices = []
seen = set()
seen, sub_seen = set(), set()
id_offsets = self.serializer.id_offsets
for node in self.oeb.toc.iterdescendants():
# Flatten toc to contain only chapters and subchapters
# Anything deeper than a subchapter is made into a subchapter
chapters = []
for node in self.oeb.toc:
try:
offset = id_offsets[node.href]
label = self.cncx[node.title]
except:
self.log.warn('TOC item %s not found in document'%node.href)
self.log.warn('TOC item %s [%s] not found in document'%(
node.title, node.href))
continue
if offset in seen:
continue
seen.add(offset)
index = IndexEntry(offset, label)
indices.append(index)
indices.sort(key=lambda x:x.offset)
subchapters = []
chapters.append((offset, label, subchapters))
# Set lengths
for descendant in node.iterdescendants():
try:
offset = id_offsets[descendant.href]
label = self.cncx[descendant.title]
except:
self.log.warn('TOC item %s [%s] not found in document'%(
descendant.title, descendant.href))
continue
if offset in sub_seen:
continue
sub_seen.add(offset)
subchapters.append((offset, label))
subchapters.sort(key=lambda x:x[0])
chapters.sort(key=lambda x:x[0])
chapters = [(BookIndexEntry(x[0], x[1]), [
BookIndexEntry(y[0], y[1]) for y in x[2]]) for x in chapters]
def set_length(indices):
for i, index in enumerate(indices):
try:
next_offset = indices[i+1].offset
@ -507,20 +698,45 @@ class Indexer(object): # {{{
next_offset = self.serializer.body_end_offset
index.length = next_offset - index.offset
# Remove empty nodes
indices = [i for i in indices if i.length > 0]
# Set chapter and subchapter lengths
set_length([x[0] for x in chapters])
for x in chapters:
set_length(x[1])
# Set index values
for i, index in enumerate(indices):
index.index = i
# Remove empty chapters
chapters = [x for x in chapters if x[0].length > 0]
# Set lengths again to close up any gaps left by filtering
for i, index in enumerate(indices):
try:
next_offset = indices[i+1].offset
except:
next_offset = self.serializer.body_end_offset
index.length = next_offset - index.offset
# Remove invalid subchapters
for i, x in enumerate(list(chapters)):
chapter, subchapters = x
ok_subchapters = []
for sc in subchapters:
if sc.offset < chapter.next_offset and sc.length > 0:
ok_subchapters.append(sc)
chapters[i] = (chapter, ok_subchapters)
# Reset chapter and subchapter lengths in case any were removed
set_length([x[0] for x in chapters])
for x in chapters:
set_length(x[1])
# Set index and depth values
indices = []
for index, x in enumerate(chapters):
x[0].index = index
indices.append(x[0])
for chapter, subchapters in chapters:
for sc in subchapters:
index += 1
sc.index = index
sc.parent_index = chapter.index
indices.append(sc)
sc.depth = 1
self.book_has_subchapters = True
if subchapters:
chapter.first_child_index = subchapters[0].index
chapter.last_child_index = subchapters[-1].index
return indices
@ -536,11 +752,12 @@ class Indexer(object): # {{{
id_offsets = self.serializer.id_offsets
periodical = IndexEntry(periodical_node_offset,
periodical = PeriodicalIndexEntry(periodical_node_offset,
self.cncx[periodical_node.title],
class_offset=self.cncx[periodical_node.klass])
self.cncx[periodical_node.klass], 0)
periodical.length = periodical_node_size
periodical.first_child_index = 1
periodical.image_index = self.masthead_offset
seen_sec_offsets = set()
seen_art_offsets = set()
@ -556,7 +773,7 @@ class Indexer(object): # {{{
if offset in seen_sec_offsets:
continue
seen_sec_offsets.add(offset)
section = IndexEntry(offset, label, class_offset=klass, depth=1)
section = PeriodicalIndexEntry(offset, label, klass, 1)
section.parent_index = 0
for art in sec:
try:
@ -568,9 +785,11 @@ class Indexer(object): # {{{
if offset in seen_art_offsets:
continue
seen_art_offsets.add(offset)
article = IndexEntry(offset, label, class_offset=klass,
depth=2)
article = PeriodicalIndexEntry(offset, label, klass, 2)
normalized_articles.append(article)
article.author_offset = self.cncx[art.author]
article.desc_offset = self.cncx[art.description]
if normalized_articles:
normalized_articles.sort(key=lambda x:x.offset)
normalized_sections.append((section, normalized_articles))

View File

@ -11,7 +11,7 @@ import re, random, time
from cStringIO import StringIO
from struct import pack
from calibre.ebooks import normalize
from calibre.ebooks import normalize, generate_masthead
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
from calibre.ebooks.mobi.writer2.serializer import Serializer
from calibre.ebooks.compression.palmdoc import compress_doc
@ -19,7 +19,7 @@ from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.utils.filenames import ascii_filename
from calibre.ebooks.mobi.writer2 import (PALMDOC, UNCOMPRESSED, RECORD_SIZE)
from calibre.ebooks.mobi.utils import (rescale_image, encint,
encode_trailing_data, align_block)
encode_trailing_data, align_block, detect_periodical)
from calibre.ebooks.mobi.writer2.indexer import Indexer
EXTH_CODES = {
@ -34,6 +34,12 @@ EXTH_CODES = {
'rights': 109,
'type': 111,
'source': 112,
'versionnumber': 114,
'startreading': 116,
'coveroffset': 201,
'thumboffset': 202,
'hasfakecover': 203,
'lastupdatetime': 502,
'title': 503,
}
@ -77,13 +83,16 @@ class MobiWriter(object):
self.write_content()
def generate_content(self):
self.map_image_names()
self.is_periodical = detect_periodical(self.oeb.toc, self.oeb.log)
# Image records are stored in their own list, they are merged into the
# main record list at the end
self.generate_images()
self.generate_text()
# The uncrossable breaks trailing entries come before the indexing
# trailing entries
self.write_uncrossable_breaks()
# Index records come after text records
self.generate_index()
self.write_uncrossable_breaks()
# Image records come after index records
self.generate_images()
# Indexing {{{
def generate_index(self):
@ -91,22 +100,18 @@ class MobiWriter(object):
try:
self.indexer = Indexer(self.serializer, self.last_text_record_idx,
len(self.records[self.last_text_record_idx]),
self.masthead_offset, self.is_periodical,
self.opts, self.oeb)
except:
self.log.exception('Failed to generate MOBI index:')
else:
self.primary_index_record_idx = len(self.records)
for i in xrange(len(self.records)):
for i in xrange(self.last_text_record_idx + 1):
if i == 0: continue
tbs = self.indexer.get_trailing_byte_sequence(i)
self.records[i] += encode_trailing_data(tbs)
self.records.extend(self.indexer.records)
@property
def is_periodical(self):
return (self.primary_index_record_idx is None or not
self.indexer.is_periodical)
# }}}
def write_uncrossable_breaks(self): # {{{
@ -136,58 +141,54 @@ class MobiWriter(object):
# }}}
# Images {{{
def map_image_names(self):
'''
Map image names to record indices, ensuring that the masthead image if
present has index number 1.
'''
index = 1
self.images = images = {}
mh_href = None
if 'masthead' in self.oeb.guide:
mh_href = self.oeb.guide['masthead'].href
images[mh_href] = 1
index += 1
for item in self.oeb.manifest.values():
if item.media_type in OEB_RASTER_IMAGES:
if item.href == mh_href: continue
images[item.href] = index
index += 1
def generate_images(self):
self.oeb.logger.info('Serializing images...')
images = [(index, href) for href, index in self.images.iteritems()]
images.sort()
self.first_image_record = None
for _, href in images:
item = self.oeb.manifest.hrefs[href]
oeb = self.oeb
oeb.logger.info('Serializing images...')
self.image_records = []
self.image_map = {}
mh_href = self.masthead_offset = None
if 'masthead' in oeb.guide:
mh_href = oeb.guide['masthead'].href
elif self.is_periodical:
# Generate a default masthead
data = generate_masthead(unicode(self.oeb.metadata('title')[0]))
self.image_records.append(data)
self.masthead_offset = 0
cover_href = self.cover_offset = self.thumbnail_offset = None
if (oeb.metadata.cover and
unicode(oeb.metadata.cover[0]) in oeb.manifest.ids):
cover_id = unicode(oeb.metadata.cover[0])
item = oeb.manifest.ids[cover_id]
cover_href = item.href
for item in self.oeb.manifest.values():
if item.media_type not in OEB_RASTER_IMAGES: continue
try:
data = rescale_image(item.data)
except:
self.oeb.logger.warn('Bad image file %r' % item.href)
oeb.logger.warn('Bad image file %r' % item.href)
continue
finally:
item.unload_data_from_memory()
self.records.append(data)
if self.first_image_record is None:
self.first_image_record = len(self.records) - 1
else:
self.image_map[item.href] = len(self.image_records)
self.image_records.append(data)
def add_thumbnail(self, item):
if item.href == mh_href:
self.masthead_offset = len(self.image_records) - 1
elif item.href == cover_href:
self.cover_offset = len(self.image_records) - 1
try:
data = rescale_image(item.data, dimen=MAX_THUMB_DIMEN,
maxsizeb=MAX_THUMB_SIZE)
except IOError:
self.oeb.logger.warn('Bad image file %r' % item.href)
return None
manifest = self.oeb.manifest
id, href = manifest.generate('thumbnail', 'thumbnail.jpeg')
manifest.add(id, href, 'image/jpeg', data=data)
index = len(self.images) + 1
self.images[href] = index
self.records.append(data)
return index
except:
oeb.logger.warn('Failed to generate thumbnail')
else:
self.image_records.append(data)
self.thumbnail_offset = len(self.image_records) - 1
finally:
item.unload_data_from_memory()
# }}}
@ -195,12 +196,13 @@ class MobiWriter(object):
def generate_text(self):
self.oeb.logger.info('Serializing markup content...')
self.serializer = Serializer(self.oeb, self.images,
self.serializer = Serializer(self.oeb, self.image_map,
write_page_breaks_after_item=self.write_page_breaks_after_item)
text = self.serializer()
self.text_length = len(text)
text = StringIO(text)
nrecords = 0
records_size = 0
if self.compression != UNCOMPRESSED:
self.oeb.logger.info(' Compressing markup content...')
@ -214,9 +216,15 @@ class MobiWriter(object):
data += pack(b'>B', len(overlap))
self.records.append(data)
records_size += len(data)
nrecords += 1
self.last_text_record_idx = nrecords
self.first_non_text_record_idx = nrecords + 1
# Pad so that the next records starts at a 4 byte boundary
if records_size % 4 != 0:
self.records.append(b'\x00'*(records_size % 4))
self.first_non_text_record_idx += 1
def read_text_record(self, text):
'''
@ -273,9 +281,13 @@ class MobiWriter(object):
def generate_record0(self): # MOBI header {{{
metadata = self.oeb.metadata
exth = self.build_exth()
first_image_record = None
if self.image_records:
first_image_record = len(self.records)
self.records.extend(self.image_records)
last_content_record = len(self.records) - 1
# FCIS/FLIS (Seem to server no purpose)
# FCIS/FLIS (Seems to serve no purpose)
flis_number = len(self.records)
self.records.append(
b'FLIS\0\0\0\x08\0\x41\0\0\0\0\0\0\xff\xff\xff\xff\0\x01\0\x03\0\0\0\x03\0\0\0\x01'+
@ -322,6 +334,8 @@ class MobiWriter(object):
if self.indexer.is_flat_periodical:
bt = 0x102
elif self.indexer.is_periodical:
# If you change this, remember to change the cdetype in the EXTH
# header as well
bt = 0x103
record0.write(pack(b'>IIIII',
@ -331,14 +345,19 @@ class MobiWriter(object):
record0.write(b'\xff' * 8)
# 0x20 - 0x23 : Secondary index record
record0.write(pack(b'>I', 0xffffffff))
sir = 0xffffffff
if (self.primary_index_record_idx is not None and
self.indexer.secondary_record_offset is not None):
sir = (self.primary_index_record_idx +
self.indexer.secondary_record_offset)
record0.write(pack(b'>I', sir))
# 0x24 - 0x3f : Unknown
record0.write(b'\xff' * 28)
# 0x40 - 0x43 : Offset of first non-text record
record0.write(pack(b'>I',
self.last_text_record_idx + 1))
self.first_non_text_record_idx))
# 0x44 - 0x4b : title offset, title length
record0.write(pack(b'>II',
@ -354,8 +373,7 @@ class MobiWriter(object):
# 0x58 - 0x5b : Format version
# 0x5c - 0x5f : First image record number
record0.write(pack(b'>II',
6, self.first_image_record if self.first_image_record else
len(self.records)-1))
6, first_image_record if first_image_record else len(self.records)))
# 0x60 - 0x63 : First HUFF/CDIC record number
# 0x64 - 0x67 : Number of HUFF/CDIC records
@ -493,6 +511,10 @@ class MobiWriter(object):
# Write cdetype
if self.is_periodical:
# If you set the book type header field to 0x101 use NWPR here if
# you use 0x103 use MAGZ
data = b'MAGZ'
else:
data = b'EBOK'
exth.write(pack(b'>II', 501, len(data)+8))
exth.write(data)
@ -504,33 +526,41 @@ class MobiWriter(object):
elif oeb.metadata['timestamp']:
datestr = str(oeb.metadata['timestamp'][0])
if datestr is not None:
if datestr is None:
raise ValueError("missing date or timestamp")
datestr = bytes(datestr)
exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8))
exth.write(datestr)
nrecs += 1
else:
raise NotImplementedError("missing date or timestamp needed for mobi_periodical")
# Write the same creator info as kindlegen 1.2
for code, val in [(204, 201), (205, 1), (206, 2), (207, 33307)]:
exth.write(pack(b'>II', code, 12))
exth.write(pack(b'>I', val))
if self.is_periodical:
exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8))
exth.write(datestr)
nrecs += 1
if (oeb.metadata.cover and
unicode(oeb.metadata.cover[0]) in oeb.manifest.ids):
id = unicode(oeb.metadata.cover[0])
item = oeb.manifest.ids[id]
href = item.href
if href in self.images:
index = self.images[href] - 1
exth.write(pack(b'>III', 0xc9, 0x0c, index))
exth.write(pack(b'>III', 0xcb, 0x0c, 0))
if self.is_periodical:
# Pretend to be amazon's super secret periodical generator
vals = {204:201, 205:2, 206:0, 207:101}
else:
# Pretend to be kindlegen 1.2
vals = {204:201, 205:1, 206:2, 207:33307}
for code, val in vals.iteritems():
exth.write(pack(b'>III', code, 12, val))
nrecs += 1
if self.cover_offset is not None:
exth.write(pack(b'>III', EXTH_CODES['coveroffset'], 12,
self.cover_offset))
exth.write(pack(b'>III', EXTH_CODES['hasfakecover'], 12, 0))
nrecs += 2
index = self.add_thumbnail(item)
if index is not None:
exth.write(pack(b'>III', 0xca, 0x0c, index - 1))
if self.thumbnail_offset is not None:
exth.write(pack(b'>III', EXTH_CODES['thumboffset'], 12,
self.thumbnail_offset))
nrecs += 1
if self.serializer.start_offset is not None:
exth.write(pack(b'>III', EXTH_CODES['startreading'], 12,
self.serializer.start_offset))
nrecs += 1
exth = exth.getvalue()
@ -550,7 +580,7 @@ class MobiWriter(object):
now = int(time.time())
nrecords = len(self.records)
self.write(title, pack(b'>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
b'BOOK', b'MOBI', pack(b'>IIH', nrecords, 0, nrecords))
b'BOOK', b'MOBI', pack(b'>IIH', (2*nrecords)-1, 0, nrecords))
offset = self.tell() + (8 * nrecords) + 2
for i, record in enumerate(self.records):
self.write(pack(b'>I', offset), b'\0', pack(b'>I', 2*i)[1:])

View File

@ -39,6 +39,10 @@ class Serializer(object):
self.logger = oeb.logger
self.write_page_breaks_after_item = write_page_breaks_after_item
# If not None, this is a number pointing to the location at which to
# open the MOBI file on the Kindle
self.start_offset = None
# Mapping of hrefs (urlnormalized) to the offset in the buffer where
# the resource pointed to by the href lives. Used at the end to fill in
# the correct values into all filepos="..." links.
@ -106,6 +110,7 @@ class Serializer(object):
self.serialize_head()
self.serialize_body()
buf.write(b'</html>')
self.end_offset = buf.tell()
self.fixup_links()
return buf.getvalue()
@ -144,6 +149,8 @@ class Serializer(object):
buf.write(b'title="')
self.serialize_text(ref.title, quot=True)
buf.write(b'" ')
if ref.title == 'start':
self._start_href = ref.href
self.serialize_href(ref.href)
# Space required or won't work, I kid you not
buf.write(b' />')
@ -200,20 +207,18 @@ class Serializer(object):
self.breaks.append(buf.tell() - 1)
self.id_offsets[urlnormalize(item.href)] = buf.tell()
if item.is_section_start:
buf.write(b'<div>')
buf.write(b'<a ></a> ')
if item.is_article_start:
buf.write(b'<div>')
buf.write(b'<a ></a> <a ></a>')
for elem in item.data.find(XHTML('body')):
self.serialize_elem(elem, item)
if item.is_article_end:
# Kindle periodical article end marker
buf.write(b'<div></div>')
if self.write_page_breaks_after_item:
buf.write(b'<mbp:pagebreak/>')
if item.is_article_end:
buf.write(b'</div>')
# Kindle periodical article end marker
buf.write(b'<a ></a> <a ></a>')
if item.is_section_end:
buf.write(b'</div>')
buf.write(b' <a ></a>')
self.anchor_offset = None
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
@ -283,6 +288,7 @@ class Serializer(object):
buf = self.buf
id_offsets = self.id_offsets
for href, hoffs in self.href_offsets.items():
is_start = (href and href == getattr(self, '_start_href', None))
# Iterate over all filepos items
if href not in id_offsets:
self.logger.warn('Hyperlink target %r not found' % href)
@ -290,6 +296,8 @@ class Serializer(object):
href, _ = urldefrag(href)
if href in self.id_offsets:
ioff = self.id_offsets[href]
if is_start:
self.start_offset = ioff
for hoff in hoffs:
buf.seek(hoff)
buf.write(b'%010d' % ioff)

View File

@ -61,9 +61,11 @@ def meta_info_to_oeb_metadata(mi, m, log, override_input_metadata=False):
m.add('identifier', val, scheme=typ.upper())
if override_input_metadata and not set_isbn:
m.filter('identifier', lambda x: x.scheme.lower() == 'isbn')
if not mi.is_null('language'):
if not mi.is_null('languages'):
m.clear('language')
m.add('language', mi.language)
for lang in mi.languages:
if lang and lang.lower() not in ('und', ''):
m.add('language', lang)
if not mi.is_null('series_index'):
m.clear('series_index')
m.add('series_index', mi.format_series_index())

View File

@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en'
import sys, struct, zlib, bz2, os
from calibre import guess_type
from calibre.utils.filenames import ascii_filename
class FileStream:
def IsBinary(self):
@ -158,7 +157,7 @@ class SNBFile:
f.fileBody = open(os.path.join(tdir,fileName), 'rb').read()
f.fileName = fileName.replace(os.sep, '/')
if isinstance(f.fileName, unicode):
f.fileName = ascii_filename(f.fileName).encode('ascii')
f.fileName = f.fileName.encode("ascii", "ignore");
self.files.append(f)
def AppendBinary(self, fileName, tdir):
@ -168,7 +167,7 @@ class SNBFile:
f.fileBody = open(os.path.join(tdir,fileName), 'rb').read()
f.fileName = fileName.replace(os.sep, '/')
if isinstance(f.fileName, unicode):
f.fileName = ascii_filename(f.fileName).encode('ascii')
f.fileName = f.fileName.encode("ascii", "ignore");
self.files.append(f)
def GetFileStream(self, fileName):

View File

@ -40,7 +40,7 @@ if isosx:
gprefs.defaults['action-layout-toolbar-device'] = (
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
'Send To Device', None, None, 'Location Manager', None, None,
'Fetch News', 'Save To Disk', 'Connect Share', None,
'Fetch News', 'Store', 'Save To Disk', 'Connect Share', None,
'Remove Books',
)
else:
@ -55,7 +55,7 @@ else:
gprefs.defaults['action-layout-toolbar-device'] = (
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
'Send To Device', None, None, 'Location Manager', None, None,
'Fetch News', 'Save To Disk', 'Connect Share', None,
'Fetch News', 'Save To Disk', 'Store', 'Connect Share', None,
'Remove Books', None, 'Help', 'Preferences',
)
@ -94,9 +94,10 @@ gprefs.defaults['book_display_fields'] = [
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False),
('last_modified', False), ('size', False), ('languages', False),
]
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True
# }}}
@ -169,7 +170,9 @@ def _config(): # {{{
c.add_opt('scheduler_search_history', default=[],
help='Search history for the recipe scheduler')
c.add_opt('plugin_search_history', default=[],
help='Search history for the recipe scheduler')
help='Search history for the plugin preferences')
c.add_opt('shortcuts_search_history', default=[],
help='Search history for the keyboard preferences')
c.add_opt('worker_limit', default=6,
help=_(
'Maximum number of simultaneous conversion/news download jobs. '
@ -423,6 +426,10 @@ class FileIconProvider(QFileIconProvider):
'rtf' : 'rtf',
'odt' : 'odt',
'snb' : 'snb',
'djv' : 'djvu',
'djvu' : 'djvu',
'xps' : 'xps',
'oxps' : 'xps',
}
def __init__(self):

View File

@ -8,9 +8,13 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from zipfile import ZipFile
from PyQt4.Qt import QToolButton, QAction, QIcon, QObject
from PyQt4.Qt import (QToolButton, QAction, QIcon, QObject, QMenu,
QKeySequence)
from calibre import prints
from calibre.gui2 import Dispatcher
from calibre.gui2.keyboard import NameConflict
class InterfaceAction(QObject):
@ -66,6 +70,14 @@ class InterfaceAction(QObject):
#: shortcut must be a translated string if not None
action_spec = ('text', 'icon', None, None)
#: If True, a menu is automatically created and added to self.qaction
action_add_menu = False
#: If True, a clone of self.qaction is added to the menu of self.qaction
#: If you want the text of this action to be different from that of
#: self.qaction, set this variable to the new text
action_menu_clone_qaction = False
#: Set of locations to which this action must not be added.
#: See :attr:`all_locations` for a list of possible locations
dont_add_to = frozenset([])
@ -75,7 +87,8 @@ class InterfaceAction(QObject):
dont_remove_from = frozenset([])
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device'])
'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device',
'context-menu-cover-browser'])
#: Type of action
#: 'current' means acts on the current view
@ -94,8 +107,16 @@ class InterfaceAction(QObject):
self.Dispatcher = partial(Dispatcher, parent=self)
self.create_action()
self.gui.addAction(self.qaction)
self.gui.addAction(self.menuless_qaction)
self.genesis()
@property
def unique_name(self):
bn = self.__class__.__name__
if getattr(self.interface_action_base_plugin, 'name'):
bn = self.interface_action_base_plugin.name
return u'Interface Action: %s (%s)'%(bn, self.name)
def create_action(self, spec=None, attr='qaction'):
if spec is None:
spec = self.action_spec
@ -104,20 +125,75 @@ class InterfaceAction(QObject):
action = QAction(QIcon(I(icon)), text, self.gui)
else:
action = QAction(text, self.gui)
action.setAutoRepeat(self.auto_repeat)
if attr == 'qaction':
mt = (action.text() if self.action_menu_clone_qaction is True else
unicode(self.action_menu_clone_qaction))
self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui)
ma.triggered.connect(action.trigger)
for a in ((action, ma) if attr == 'qaction' else (action,)):
a.setAutoRepeat(self.auto_repeat)
text = tooltip if tooltip else text
action.setToolTip(text)
action.setStatusTip(text)
action.setWhatsThis(text)
action.setAutoRepeat(False)
if shortcut:
if isinstance(shortcut, list):
action.setShortcuts(shortcut)
else:
action.setShortcut(shortcut)
a.setToolTip(text)
a.setStatusTip(text)
a.setWhatsThis(text)
shortcut_action = action
desc = tooltip if tooltip else None
if attr == 'qaction':
shortcut_action = ma
if shortcut is not None:
keys = ((shortcut,) if isinstance(shortcut, basestring) else
tuple(shortcut))
if spec[0] and not (attr=='qaction' and self.popup_type ==
QToolButton.InstantPopup):
try:
self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr,
unicode(spec[0]), default_keys=keys,
action=shortcut_action, description=desc,
group=self.action_spec[0])
except NameConflict as e:
try:
prints(unicode(e))
except:
pass
shortcut_action.setShortcuts([QKeySequence(key,
QKeySequence.PortableText) for key in keys])
if attr is not None:
setattr(self, attr, action)
if attr == 'qaction' and self.action_add_menu:
menu = QMenu()
action.setMenu(menu)
if self.action_menu_clone_qaction:
menu.addAction(self.menuless_qaction)
return action
def create_menu_action(self, menu, unique_name, text, icon=None, shortcut=None,
description=None, triggered=None):
ac = menu.addAction(text)
if icon is not None:
if not isinstance(icon, QIcon):
icon = QIcon(I(icon))
ac.setIcon(icon)
keys = ()
if shortcut is not None and shortcut is not False:
keys = ((shortcut,) if isinstance(shortcut, basestring) else
tuple(shortcut))
unique_name = '%s : menu action : %s'%(self.unique_name, unique_name)
if description is not None:
ac.setToolTip(description)
ac.setStatusTip(description)
ac.setWhatsThis(description)
if shortcut is not False:
self.gui.keyboard.register_shortcut(unique_name,
unicode(text), default_keys=keys,
action=ac, description=description, group=self.action_spec[0])
if triggered is not None:
ac.triggered.connect(triggered)
return ac
def load_resources(self, names):
'''
If this plugin comes in a ZIP file (user added plugin), this method

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import QPixmap, QMenu, QTimer
from PyQt4.Qt import QPixmap, QTimer
from calibre.gui2 import error_dialog, choose_files, \
@ -48,28 +48,29 @@ class AddAction(InterfaceAction):
_('Add books to the calibre library/device from files on your computer')
, _('A'))
action_type = 'current'
action_add_menu = True
action_menu_clone_qaction = _('Add books from a single directory')
def genesis(self):
self._add_filesystem_book = self.Dispatcher(self.__add_filesystem_book)
self.add_menu = QMenu()
self.add_menu.addAction(_('Add books from a single directory'),
self.add_books)
self.add_menu.addAction(_('Add books from directories, including '
self.add_menu = self.qaction.menu()
ma = partial(self.create_menu_action, self.add_menu)
ma('recursive-single', _('Add books from directories, including '
'sub-directories (One book per directory, assumes every ebook '
'file is the same book in a different format)'),
'file is the same book in a different format)')).triggered.connect(
self.add_recursive_single)
self.add_menu.addAction(_('Add books from directories, including '
ma('recursive-multiple', _('Add books from directories, including '
'sub directories (Multiple books per directory, assumes every '
'ebook file is a different book)'), self.add_recursive_multiple)
'ebook file is a different book)')).triggered.connect(
self.add_recursive_multiple)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty, _('Shift+Ctrl+E'))
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
ma('add-empty', _('Add Empty book. (Book entry with no formats)'),
shortcut=_('Shift+Ctrl+E')).triggered.connect(self.add_empty)
ma('add-isbn', _('Add from ISBN')).triggered.connect(self.add_from_isbn)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add files to selected book records'),
self.add_formats, _('Shift+A'))
ma('add-formats', _('Add files to selected book records'),
triggered=self.add_formats, shortcut=_('Shift+A'))
self.qaction.setMenu(self.add_menu)
self.qaction.triggered.connect(self.add_books)
def location_selected(self, loc):
@ -83,7 +84,8 @@ class AddAction(InterfaceAction):
view = self.gui.library_view
rows = view.selectionModel().selectedRows()
if not rows:
return
return error_dialog(self.gui, _('No books selected'),
_('Cannot add files as no books are selected'), show=True)
ids = [view.model().id(r) for r in rows]
if len(ids) > 1 and not question_dialog(self.gui,

View File

@ -8,13 +8,14 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog, QToolButton
from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog,
QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QIcon, QSize)
from calibre import isbytestring
from calibre.constants import filesystem_encoding, iswindows
from calibre.utils.config import prefs
from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog,
question_dialog, info_dialog, open_local_file)
question_dialog, info_dialog, open_local_file, choose_dir)
from calibre.library.database2 import LibraryDatabase2
from calibre.gui2.actions import InterfaceAction
@ -76,29 +77,83 @@ class LibraryUsageStats(object): # {{{
self.write_stats()
# }}}
class MovedDialog(QDialog): # {{{
def __init__(self, stats, location, parent=None):
QDialog.__init__(self, parent)
self.setWindowTitle(_('No library found'))
self._l = l = QGridLayout(self)
self.setLayout(l)
self.stats, self.location = stats, location
loc = self.oldloc = location.replace('/', os.sep)
self.header = QLabel(_('No existing calibre library was found at %s. '
'If the library was moved, select its new location below. '
'Otherwise calibre will forget this library.')%loc)
self.header.setWordWrap(True)
ncols = 2
l.addWidget(self.header, 0, 0, 1, ncols)
self.cl = QLabel('<br><b>'+_('New location of this library:'))
l.addWidget(self.cl, 1, 0, 1, ncols)
self.loc = QLineEdit(loc, self)
l.addWidget(self.loc, 2, 0, 1, 1)
self.cd = QToolButton(self)
self.cd.setIcon(QIcon(I('document_open.png')))
self.cd.clicked.connect(self.choose_dir)
l.addWidget(self.cd, 2, 1, 1, 1)
self.bb = QDialogButtonBox(self)
b = self.bb.addButton(_('Library moved'), self.bb.AcceptRole)
b.setIcon(QIcon(I('ok.png')))
b = self.bb.addButton(_('Forget library'), self.bb.RejectRole)
b.setIcon(QIcon(I('edit-clear.png')))
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
l.addWidget(self.bb, 3, 0, 1, ncols)
self.resize(self.sizeHint() + QSize(100, 50))
def choose_dir(self):
d = choose_dir(self, 'library moved choose new loc',
_('New library location'), default_dir=self.oldloc)
if d is not None:
self.loc.setText(d)
def reject(self):
self.stats.remove(self.location)
QDialog.reject(self)
def accept(self):
newloc = unicode(self.loc.text())
if not LibraryDatabase2.exists_at(newloc):
error_dialog(self, _('No library found'),
_('No existing calibre library found at %s')%newloc,
show=True)
return
self.stats.rename(self.location, newloc)
self.newloc = newloc
QDialog.accept(self)
# }}}
class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library'
action_spec = (_('%d books'), 'lt.png',
action_spec = (_('Choose Library'), 'lt.png',
_('Choose calibre library to work with'), None)
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_add_menu = True
action_menu_clone_qaction = _('Switch/create library...')
def genesis(self):
self.base_text = _('%d books')
self.count_changed(0)
self.qaction.triggered.connect(self.choose_library,
type=Qt.QueuedConnection)
self.action_choose = self.menuless_qaction
self.stats = LibraryUsageStats()
self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else
QToolButton.MenuButtonPopup)
self.create_action(spec=(_('Switch/create library...'), 'lt.png', None,
None), attr='action_choose')
self.action_choose.triggered.connect(self.choose_library,
type=Qt.QueuedConnection)
self.choose_menu = QMenu(self.gui)
self.qaction.setMenu(self.choose_menu)
self.choose_menu = self.qaction.menu()
if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
self.choose_menu.addAction(self.action_choose)
@ -110,7 +165,7 @@ class ChooseLibraryAction(InterfaceAction):
self.delete_menu = QMenu(_('Remove library'))
self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu)
ac = self.create_action(spec=(_('Pick a random book'), 'catalog.png',
ac = self.create_action(spec=(_('Pick a random book'), 'random.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.pick_random)
self.choose_menu.addAction(ac)
@ -152,10 +207,7 @@ class ChooseLibraryAction(InterfaceAction):
self.choose_menu.addMenu(self.maintenance_menu)
def pick_random(self, *args):
import random
pick = random.randint(0, self.gui.library_view.model().rowCount(None))
self.gui.library_view.set_current_row(pick)
self.gui.library_view.scroll_to_row(pick)
self.gui.iactions['Pick Random Book'].pick_random()
def library_name(self):
db = self.gui.library_view.model().db
@ -344,13 +396,13 @@ class ChooseLibraryAction(InterfaceAction):
loc = location.replace('/', os.sep)
exists = self.gui.library_view.model().db.exists_at(loc)
if not exists:
warning_dialog(self.gui, _('No library found'),
_('No existing calibre library was found at %s.'
' It will be removed from the list of known'
' libraries.')%loc, show=True)
self.stats.remove(location)
d = MovedDialog(self.stats, location, self.gui)
ret = d.exec_()
self.build_menus()
self.gui.iactions['Copy To Library'].build_menus()
if ret == d.Accepted:
loc = d.newloc.replace('/', os.sep)
else:
return
prefs['library_path'] = loc
@ -382,7 +434,7 @@ class ChooseLibraryAction(InterfaceAction):
self.switch_requested(self.qs_locations[idx])
def count_changed(self, new_count):
text = self.action_spec[0]%new_count
text = self.base_text%new_count
a = self.qaction
a.setText(text)
tooltip = self.action_spec[2] + '\n\n' + text

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import QModelIndex, QMenu
from PyQt4.Qt import QModelIndex
from calibre.gui2 import error_dialog, Dispatcher
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook
@ -22,20 +22,22 @@ class ConvertAction(InterfaceAction):
action_spec = (_('Convert books'), 'convert.png', None, _('C'))
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current'
action_add_menu = True
def genesis(self):
cm = QMenu()
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
m = self.convert_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.convert_menu)
cm('convert-individual', _('Convert individually'),
icon=self.qaction.icon(), triggered=partial(self.convert_ebook,
False, bulk=False))
cm.addAction(_('Bulk convert'),
partial(self.convert_ebook, False, bulk=True))
cm.addSeparator()
ac = cm.addAction(
_('Create a catalog of the books in your calibre library'))
ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog)
self.qaction.setMenu(cm)
cm('convert-bulk', _('Bulk convert'),
triggered=partial(self.convert_ebook, False, bulk=True))
m.addSeparator()
cm('create-catalog',
_('Create a catalog of the books in your calibre library'),
icon='catalog.png', shortcut=False,
triggered=self.gui.iactions['Generate Catalog'].generate_catalog)
self.qaction.triggered.connect(self.convert_ebook)
self.convert_menu = cm
self.conversion_jobs = {}
def location_selected(self, loc):

View File

@ -9,12 +9,13 @@ import os
from functools import partial
from threading import Thread
from PyQt4.Qt import QMenu, QToolButton
from PyQt4.Qt import QToolButton
from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import error_dialog, Dispatcher, warning_dialog
from calibre.gui2 import error_dialog, Dispatcher, warning_dialog, gprefs
from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.utils.config import prefs, tweaks
from calibre.utils.date import now
class Worker(Thread): # {{{
@ -55,6 +56,8 @@ class Worker(Thread): # {{{
for i, x in enumerate(self.ids):
mi = self.db.get_metadata(x, index_is_id=True, get_cover=True,
cover_as_data=True)
if not gprefs['preserve_date_on_ctl']:
mi.timestamp = now()
self.progress(i, mi.title)
fmts = self.db.formats(x, index_is_id=True)
if not fmts: fmts = []
@ -65,14 +68,37 @@ class Worker(Thread): # {{{
as_path=True)
if p:
paths.append(p)
added = False
automerged = False
if prefs['add_formats_to_existing']:
identical_book_list = newdb.find_identical_books(mi)
if identical_book_list: # books with same author and nearly same title exist in newdb
added = True
automerged = True
seen_fmts = set()
for identical_book in identical_book_list:
self.add_formats(identical_book, paths, newdb, replace=False)
if not added:
ib_fmts = newdb.formats(identical_book, index_is_id=True)
if ib_fmts:
seen_fmts |= set(ib_fmts.split(','))
replace = gprefs['automerge'] == 'overwrite'
self.add_formats(identical_book, paths, newdb,
replace=replace)
if gprefs['automerge'] == 'new record':
incoming_fmts = \
set([os.path.splitext(path)[-1].replace('.',
'').upper() for path in paths])
if incoming_fmts.intersection(seen_fmts):
# There was at least one duplicate format
# so create a new record and put the
# incoming formats into it
# We should arguably put only the duplicate
# formats, but no real harm is done by having
# all formats
newdb.import_book(mi, paths, notify=False, import_hooks=False,
apply_import_tags=tweaks['add_new_book_tags_when_importing_books'],
preserve_uuid=False)
if not automerged:
newdb.import_book(mi, paths, notify=False, import_hooks=False,
apply_import_tags=tweaks['add_new_book_tags_when_importing_books'],
preserve_uuid=self.delete_after)
@ -95,10 +121,10 @@ class CopyToLibraryAction(InterfaceAction):
popup_type = QToolButton.InstantPopup
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
action_type = 'current'
action_add_menu = True
def genesis(self):
self.menu = QMenu(self.gui)
self.qaction.setMenu(self.menu)
self.menu = self.qaction.menu()
@property
def stats(self):

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QMenu, QObject, QTimer
from PyQt4.Qt import QObject, QTimer
from calibre.gui2 import error_dialog, question_dialog
from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
@ -18,7 +18,7 @@ from calibre.utils.recycle_bin import can_recycle
single_shot = partial(QTimer.singleShot, 10)
class MultiDeleter(QObject):
class MultiDeleter(QObject): # {{{
def __init__(self, gui, ids, callback):
from calibre.gui2.dialogs.progress import ProgressDialog
@ -77,32 +77,36 @@ class MultiDeleter(QObject):
error_dialog(self.gui, _('Failed to delete'),
_('Failed to delete some books, click the Show Details button'
' for details.'), det_msg='\n\n'.join(msg), show=True)
# }}}
class DeleteAction(InterfaceAction):
name = 'Remove Books'
action_spec = (_('Remove books'), 'trash.png', None, 'Del')
action_type = 'current'
action_add_menu = True
action_menu_clone_qaction = _('Remove selected books')
def genesis(self):
self.qaction.triggered.connect(self.delete_books)
self.delete_menu = QMenu()
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
self.delete_menu.addAction(
self.delete_menu = self.qaction.menu()
m = partial(self.create_menu_action, self.delete_menu)
m('delete-specific',
_('Remove files of a specific format from selected books..'),
self.delete_selected_formats)
self.delete_menu.addAction(
triggered=self.delete_selected_formats)
m('delete-except',
_('Remove all formats from selected books, except...'),
self.delete_all_but_selected_formats)
self.delete_menu.addAction(
triggered=self.delete_all_but_selected_formats)
m('delete-all',
_('Remove all formats from selected books'),
self.delete_all_formats)
self.delete_menu.addAction(
_('Remove covers from selected books'), self.delete_covers)
triggered=self.delete_all_formats)
m('delete-covers',
_('Remove covers from selected books'),
triggered=self.delete_covers)
self.delete_menu.addSeparator()
self.delete_menu.addAction(
m('delete-matching',
_('Remove matching books from device'),
self.remove_matching_books_from_device)
triggered=self.remove_matching_books_from_device)
self.qaction.setMenu(self.delete_menu)
self.delete_memory = {}

View File

@ -60,6 +60,19 @@ class ShareConnMenu(QMenu): # {{{
self.email_actions = []
if hasattr(parent, 'keyboard'):
r = parent.keyboard.register_shortcut
prefix = 'Share/Connect Menu '
gr = ConnectShareAction.action_spec[0]
for attr in ('folder', 'bambook', 'itunes'):
if not (iswindows or isosx) and attr == 'itunes':
continue
ac = getattr(self, 'connect_to_%s_action'%attr)
r(prefix + attr, unicode(ac.text()), action=ac,
group=gr)
r(prefix+' content server', _('Start/stop content server'),
action=self.toggle_server_action, group=gr)
def server_state_changed(self, running):
text = _('Start Content Server')
if running:

View File

@ -8,12 +8,12 @@ __docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import Qt, QMenu, QModelIndex, QTimer
from PyQt4.Qt import QMenu, QModelIndex, QTimer
from calibre.gui2 import error_dialog, Dispatcher, question_dialog
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.device_category_editor import DeviceCategoryEditor
from calibre.gui2.actions import InterfaceAction
from calibre.ebooks.metadata import authors_to_string
from calibre.utils.icu import sort_key
@ -24,40 +24,41 @@ class EditMetadataAction(InterfaceAction):
name = 'Edit Metadata'
action_spec = (_('Edit metadata'), 'edit_input.png', None, _('E'))
action_type = 'current'
action_add_menu = True
def genesis(self):
self.create_action(spec=(_('Merge book records'), 'merge_books.png',
None, _('M')), attr='action_merge')
md = QMenu()
md.addAction(_('Edit metadata individually'),
partial(self.edit_metadata, False, bulk=False))
md = self.qaction.menu()
cm = partial(self.create_menu_action, md)
cm('individual', _('Edit metadata individually'), icon=self.qaction.icon(),
triggered=partial(self.edit_metadata, False, bulk=False))
md.addSeparator()
md.addAction(_('Edit metadata in bulk'),
partial(self.edit_metadata, False, bulk=True))
cm('bulk', _('Edit metadata in bulk'),
triggered=partial(self.edit_metadata, False, bulk=True))
md.addSeparator()
md.addAction(_('Download metadata and covers'), self.download_metadata,
Qt.ControlModifier+Qt.Key_D)
cm('download', _('Download metadata and covers'),
triggered=partial(self.download_metadata, ids=None),
shortcut='Ctrl+D')
self.metadata_menu = md
mb = QMenu()
mb.addAction(_('Merge into first selected book - delete others'),
self.merge_books)
cm2 = partial(self.create_menu_action, mb)
cm2('merge delete', _('Merge into first selected book - delete others'),
triggered=self.merge_books)
mb.addSeparator()
mb.addAction(_('Merge into first selected book - keep others'),
partial(self.merge_books, safe_merge=True),
Qt.AltModifier+Qt.Key_M)
cm2('merge keep', _('Merge into first selected book - keep others'),
triggered=partial(self.merge_books, safe_merge=True),
shortcut='Alt+M')
mb.addSeparator()
mb.addAction(_('Merge only formats into first selected book - delete others'),
partial(self.merge_books, merge_only_formats=True),
Qt.AltModifier+Qt.ShiftModifier+Qt.Key_M)
cm2('merge formats', _('Merge only formats into first selected book - delete others'),
triggered=partial(self.merge_books, merge_only_formats=True),
shortcut='Alt+Shift+M')
self.merge_menu = mb
self.action_merge.setMenu(mb)
md.addSeparator()
md.addAction(self.action_merge)
self.action_merge = cm('merge', _('Merge book records'), icon='merge_books.png',
shortcut=_('M'), triggered=self.merge_books)
self.action_merge.setMenu(mb)
self.qaction.triggered.connect(self.edit_metadata)
self.qaction.setMenu(md)
self.action_merge.triggered.connect(self.merge_books)
def location_selected(self, loc):
enabled = loc == 'library'
@ -417,7 +418,7 @@ class EditMetadataAction(InterfaceAction):
db.set_custom(dest_id, dest_value, num=colnum)
if db.field_metadata[key]['datatype'] in \
('bool', 'int', 'float', 'rating', 'datetime') \
and not dest_value:
and dest_value is None:
db.set_custom(dest_id, src_value, num=colnum)
if db.field_metadata[key]['datatype'] == 'series' \
and not dest_value:
@ -441,7 +442,7 @@ class EditMetadataAction(InterfaceAction):
def edit_device_collections(self, view, oncard=None):
model = view.model()
result = model.get_collections_with_ids()
d = TagListEditor(self.gui, tag_to_match=None, data=result, key=sort_key)
d = DeviceCategoryEditor(self.gui, tag_to_match=None, data=result, key=sort_key)
d.exec_()
if d.result() == d.Accepted:
to_rename = d.to_rename # dict of new text to old ids

View File

@ -5,7 +5,9 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QIcon, QMenu, Qt
from functools import partial
from PyQt4.Qt import QIcon, Qt
from calibre.gui2.actions import InterfaceAction
from calibre.gui2.preferences.main import Preferences
@ -16,24 +18,23 @@ class PreferencesAction(InterfaceAction):
name = 'Preferences'
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P'))
action_add_menu = True
action_menu_clone_qaction = _('Change calibre behavior')
def genesis(self):
pm = QMenu()
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
pm = self.qaction.menu()
cm = partial(self.create_menu_action, pm)
if isosx:
pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config)
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
self.gui.run_wizard)
pm.addAction(QIcon(I('plugins/plugin_updater.png')),
_('Get plugins to enhance calibre'), self.get_plugins)
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
cm('welcome wizard', _('Run welcome wizard'),
icon='wizard.png', triggered=self.gui.run_wizard)
cm('plugin updater', _('Get plugins to enhance calibre'),
icon='plugins/plugin_updater.png', triggered=self.get_plugins)
if not DEBUG:
pm.addSeparator()
ac = pm.addAction(QIcon(I('debug.png')), _('Restart in debug mode'),
self.debug_restart)
ac.setShortcut('Ctrl+Shift+R')
self.gui.addAction(ac)
cm('restart', _('Restart in debug mode'), icon='debug.png',
triggered=self.debug_restart, shortcut='Ctrl+Shift+R')
self.qaction.setMenu(pm)
self.preferences_menu = pm
for x in (self.gui.preferences_action, self.qaction):
x.triggered.connect(self.do_config)

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import random
from calibre.gui2.actions import InterfaceAction
class PickRandomAction(InterfaceAction):
name = 'Pick Random Book'
action_spec = (_('Pick a random book'), 'random.png', 'Catalog builder', None)
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
def genesis(self):
self.qaction.triggered.connect(self.pick_random)
def pick_random(self):
pick = random.randint(0, self.gui.library_view.model().rowCount(None))
self.gui.library_view.set_current_row(pick)
self.gui.library_view.scroll_to_row(pick)

View File

@ -11,7 +11,7 @@ from calibre.gui2.actions import InterfaceAction
class RestartAction(InterfaceAction):
name = 'Restart'
action_spec = (_('&Restart'), None, None, _('Ctrl+R'))
action_spec = (_('Restart'), None, None, _('Ctrl+R'))
def genesis(self):
self.qaction.triggered.connect(self.restart)

View File

@ -38,25 +38,25 @@ class SaveToDiskAction(InterfaceAction):
name = "Save To Disk"
action_spec = (_('Save to disk'), 'save.png', None, _('S'))
action_type = 'current'
action_add_menu = True
action_menu_clone_qaction = True
def genesis(self):
self.qaction.triggered.connect(self.save_to_disk)
self.save_menu = QMenu()
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
False))
self.save_menu.addAction(_('Save to disk in a single directory'),
partial(self.save_to_single_dir, False))
self.save_menu.addAction(_('Save only %s format to disk')%
self.save_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.save_menu)
cm('single dir', _('Save to disk in a single directory'),
triggered=partial(self.save_to_single_dir, False))
cm('single format', _('Save only %s format to disk')%
prefs['output_format'].upper(),
partial(self.save_single_format_to_disk, False))
self.save_menu.addAction(
triggered=partial(self.save_single_format_to_disk, False))
cm('single dir and format',
_('Save only %s format to disk in a single directory')%
prefs['output_format'].upper(),
partial(self.save_single_fmt_to_single_dir, False))
triggered=partial(self.save_single_fmt_to_single_dir, False))
self.save_sub_menu = SaveMenu(self.gui)
self.save_sub_menu_action = self.save_menu.addMenu(self.save_sub_menu)
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
self.qaction.setMenu(self.save_menu)
def location_selected(self, loc):
enabled = loc == 'library'
@ -115,10 +115,7 @@ class SaveToDiskAction(InterfaceAction):
opts.save_cover = False
opts.write_opf = False
opts.template = opts.send_template
if single_dir:
opts.template = opts.template.split('/')[-1].strip()
if not opts.template:
opts.template = '{title} - {authors}'
opts.single_dir = single_dir
self._saver = Saver(self.gui, self.gui.library_view.model().db,
Dispatcher(self._books_saved), rows, path, opts,
spare_server=self.gui.spare_server)

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QMenu, QToolButton
from PyQt4.Qt import QToolButton
from calibre.gui2.actions import InterfaceAction
@ -17,9 +17,10 @@ class SimilarBooksAction(InterfaceAction):
action_spec = (_('Similar books...'), None, None, None)
popup_type = QToolButton.InstantPopup
action_type = 'current'
action_add_menu = True
def genesis(self):
m = QMenu(self.gui)
m = self.qaction.menu()
for text, icon, target, shortcut in [
(_('Books by same author'), 'user_profile.png', 'authors', _('Alt+A')),
(_('Books in this series'), 'books_in_series.png', 'series',
@ -31,7 +32,6 @@ class SimilarBooksAction(InterfaceAction):
m.addAction(ac)
ac.triggered.connect(partial(self.show_similar_books, target))
self.qaction.setMenu(m)
self.similar_menu = m
def show_similar_books(self, type, *args):
search, join = [], ' '

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QMenu, QIcon, QSize
from PyQt4.Qt import QIcon, QSize
from calibre.gui2 import error_dialog
from calibre.gui2.actions import InterfaceAction
@ -17,21 +17,28 @@ from calibre.gui2.dialogs.confirm_delete import confirm
class StoreAction(InterfaceAction):
name = 'Store'
action_spec = (_('Get books'), 'store.png', None, None)
action_spec = (_('Get books'), 'store.png', None, _('G'))
action_add_menu = True
action_menu_clone_qaction = _('Search for ebooks')
def genesis(self):
self.qaction.triggered.connect(self.do_search)
self.store_menu = QMenu()
self.load_menu()
def load_menu(self):
self.store_menu.clear()
self.store_menu.addAction(_('Search for ebooks'), self.search)
self.store_menu.addAction(_('Search for this author'), self.search_author)
self.store_menu.addAction(_('Search for this title'), self.search_title)
self.store_menu.addAction(_('Search for this book'), self.search_author_title)
self.store_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.store_menu)
for x, t in [('author', _('author')), ('title', _('title')),
('book', _('book'))]:
func = getattr(self, 'search_%s'%('author_title' if x == 'book'
else x))
ac = cm(x, _('Search for this %s'%t), triggered=func)
setattr(self, 'action_search_by_'+x, ac)
self.store_menu.addSeparator()
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
self.load_menu()
self.store_menu.addSeparator()
cm('choose stores', _('Choose stores'), triggered=self.choose)
def load_menu(self):
self.store_list_menu.clear()
icon = QIcon()
icon.addFile(I('donate.png'), QSize(16, 16))
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
@ -39,9 +46,6 @@ class StoreAction(InterfaceAction):
self.store_list_menu.addAction(icon, n, partial(self.open_store, p))
else:
self.store_list_menu.addAction(n, partial(self.open_store, p))
self.store_menu.addSeparator()
self.store_menu.addAction(_('Choose stores'), self.choose)
self.qaction.setMenu(self.store_menu)
def do_search(self):
return self.search()

View File

@ -6,8 +6,9 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, time
from functools import partial
from PyQt4.Qt import Qt, QMenu, QAction, pyqtSignal
from PyQt4.Qt import Qt, QAction, pyqtSignal
from calibre.constants import isosx
from calibre.gui2 import error_dialog, Dispatcher, question_dialog, config, \
@ -35,45 +36,41 @@ class ViewAction(InterfaceAction):
name = 'View'
action_spec = (_('View'), 'view.png', None, _('V'))
action_type = 'current'
action_add_menu = True
action_menu_clone_qaction = True
def genesis(self):
self.persistent_files = []
self.qaction.triggered.connect(self.view_book)
self.view_menu = QMenu()
ac = self.view_specific_action = QAction(_('View specific format'),
self.gui)
self.qaction.setMenu(self.view_menu)
ac.setShortcut(Qt.AltModifier+Qt.Key_V)
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
ac = self.view_action = QAction(self.qaction.icon(),
self.qaction.text(), self.gui)
ac.triggered.connect(self.view_book)
ac = self.create_action(spec=(_('Read a random book'), 'catalog.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.view_random)
ac = self.clear_history_action = QAction(
_('Clear recently viewed list'), self.gui)
ac.triggered.connect(self.clear_history)
self.view_action = self.menuless_qaction
self.view_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.view_menu)
self.view_specific_action = cm('specific', _('View specific format'),
shortcut='Alt+V', triggered=self.view_specific_format)
self.action_pick_random = cm('pick random', _('Read a random book'),
icon='random.png', triggered=self.view_random)
self.clear_sep1 = self.view_menu.addSeparator()
self.clear_sep2 = self.view_menu.addSeparator()
self.clear_history_action = cm('clear history',
_('Clear recently viewed list'), triggered=self.clear_history)
self.history_actions = [self.clear_sep1]
def initialization_complete(self):
self.build_menus(self.gui.current_db)
def build_menus(self, db):
self.view_menu.clear()
self.view_menu.addAction(self.view_action)
self.view_menu.addAction(self.view_specific_action)
self.view_menu.addSeparator()
self.view_menu.addAction(self.action_pick_random)
for ac in self.history_actions:
self.view_menu.removeAction(ac)
self.history_actions = []
history = db.prefs.get('gui_view_history', [])
if history:
self.view_menu.addSeparator()
self.view_menu.insertAction(self.clear_sep2, self.clear_sep1)
self.history_actions.append(self.clear_sep1)
for id_, title in history:
ac = HistoryAction(id_, title, self.view_menu)
self.view_menu.addAction(ac)
self.view_menu.insertAction(self.clear_sep2, ac)
ac.view_historical.connect(self.view_historical)
self.view_menu.addSeparator()
self.view_menu.addAction(self.clear_history_action)
self.history_actions.append(ac)
def clear_history(self):
db = self.gui.current_db
@ -207,7 +204,7 @@ class ViewAction(InterfaceAction):
self._view_books([index])
def view_random(self, *args):
self.gui.iactions['Choose Library'].pick_random()
self.gui.iactions['Pick Random Book'].pick_random()
self._view_books([self.gui.library_view.currentIndex()])
def _view_calibre_books(self, ids):

View File

@ -239,7 +239,7 @@ class DBAdder(QObject): # {{{
class Adder(QObject): # {{{
ADD_TIMEOUT = 600 # seconds
ADD_TIMEOUT = 900 # seconds (15 minutes)
def __init__(self, parent, db, callback, spare_server=None):
QObject.__init__(self, parent)

View File

@ -23,6 +23,8 @@ from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data,
gprefs)
from calibre.utils.icu import sort_key
from calibre.utils.formatter import EvalFormatter
from calibre.utils.date import is_date_undefined
from calibre.utils.localization import calibre_langcode_to_name
def render_html(mi, css, vertical, widget, all_fields=False): # {{{
table = render_data(mi, all_fields=all_fields,
@ -151,6 +153,12 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
authors.append(aut)
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u' & '.join(authors))))
elif field == 'languages':
if not mi.languages:
continue
names = filter(None, map(calibre_langcode_to_name, mi.languages))
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u', '.join(names))))
else:
val = mi.format_field(field)[-1]
if val is None:
@ -163,6 +171,10 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict(
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers),
series=prepare_string_for_xml(getattr(mi, field)))
elif metadata['datatype'] == 'datetime':
aval = getattr(mi, field)
if is_date_undefined(aval):
continue
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val)))

View File

@ -72,10 +72,11 @@ class HeuristicsWidget(Widget, Ui_Form):
return True
def load_histories(self):
val = unicode(self.opt_replace_scene_breaks.currentText())
self.opt_replace_scene_breaks.clear()
self.opt_replace_scene_breaks.lineEdit().setText('')
val = unicode(self.opt_replace_scene_breaks.currentText())
rssb_hist = gprefs.get('replace_scene_breaks_history', self.rssb_defaults)
if val in rssb_hist:
del rssb_hist[rssb_hist.index(val)]

View File

@ -6,11 +6,9 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import Qt
from calibre.gui2.convert.mobi_output_ui import Ui_Form
from calibre.gui2.convert import Widget
from calibre.gui2.widgets import FontFamilyModel
font_family_model = None
@ -26,11 +24,13 @@ class PluginWidget(Widget, Ui_Form):
['prefer_author_sort', 'rescale_images', 'toc_title',
'mobi_ignore_margins', 'mobi_toc_at_start',
'dont_compress', 'no_inline_toc',
'masthead_font','personal_doc', 'mobi_navpoints_only_deepest']
'personal_doc']#, 'mobi_navpoints_only_deepest']
)
from calibre.utils.fonts import fontconfig
self.db, self.book_id = db, book_id
'''
from calibre.utils.fonts import fontconfig
global font_family_model
if font_family_model is None:
font_family_model = FontFamilyModel()
@ -46,9 +46,11 @@ class PluginWidget(Widget, Ui_Form):
self.font_family_model = font_family_model
self.opt_masthead_font.setModel(self.font_family_model)
'''
self.initialize_options(get_option, get_help, db, book_id)
'''
def set_value_handler(self, g, val):
if unicode(g.objectName()) in 'opt_masthead_font':
idx = -1
@ -59,3 +61,4 @@ class PluginWidget(Widget, Ui_Form):
g.setCurrentIndex(idx)
return True
return False
'''

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>521</width>
<height>331</height>
<height>342</height>
</rect>
</property>
<property name="windowTitle">
@ -55,22 +55,12 @@
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Kindle options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Periodical masthead font:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_masthead_font"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -101,7 +91,7 @@
</layout>
</widget>
</item>
<item row="10" column="0">
<item row="9" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -128,13 +118,6 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_mobi_navpoints_only_deepest">
<property name="text">
<string>Use only &amp;lowest level of items in the TOC for chapter-to-chapter navigation</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

Some files were not shown because too many files have changed in this diff Show More