Lisp hackers have all defun
Innan jag börjar babbla, vilket jag för en gångs skull kommer göra till en alldeles synnerlig grad i det här inlägget, så kommer här litet kuriosa.
"Lisp has jokingly been called 'the most intelligent way to misuse a computer'. I think that description is a great compliment because it transmits the full flavor of liberation: it has assisted a number of our most gifted fellow humans in thinking previously impossible thoughts."
Edsger Dijkstra
"SQL, Lisp, and Haskell are the only programming languages that I've seen where one spends more time thinking than typing."
Philip Greenspun
Så. Medan jag de senaste veckorna lagt alldeles för mycket tid på att lära mig alldeles för litet av C++ och Qt, särskilt i proportion till mängden kod som samtidigt blivit till, så har jag förstås funderat över hur jag ska göra med projektet (H.VHS) i framtiden. Två idéer har därmed klarnat.
- Att separera fram- och bakändorna är värt besväret, inte minst därför att jag förr eller senare kommer vilja ha samma funktionalitet, fast i terminalformat.
- Metoderna för att trolla fram information om strömmar, både sådan avsedd för människor och sådan avsedd för nedladdare, bör kunna skrivas på ett så aktivt och "dynamiskt" sätt som möjligt.
I Qt är den till synes enklaste lösningen på det andra problemet att exponera QObject för QScript kod, vilket lämpligen görs på ett sådant sätt att metoderna tycks vara skrivna i ett någorlunda lättfattligt, specialiserat skriptspråk, "dynamiskt" i den bemärkelsen att man kan ändra i metoderna utan att behöva kompilera om själva programmet, och att metoderna kan uppdateras genom att man helt enkelt ersätter metod-filer med nya versioner som hämtas från något repository.
I praktiken innebär detta att H.VHS blir inte mycket mera än en glorifierad skripttolk. Vid den punkten stöter tanken alltså samman med följande lilla regel, som tillskrivs samme Philip Greenspun som yttrat det sista bland citaten ovan.
"Greenspun's Tenth Rule of Programming: any sufficiently complicated C or Fortran program contains an ad hoc informally-specified bug-ridden slow implementation of half of Common Lisp."
Förvisso skulle jag bli omätligt imponerad av mig själv om jag lyckades implementera hälften av Common Lisp, hur illa det än fungerade, men ännu hellre slipper jag att ens komma i närheten. Beroende på hur man ser på saken så är Common Lisp ett mycket litet eller ett mycket stort programspråk – det beror på om alla funktionsbibliotek ska räknas till språket eller inte. (Parallellt kan man fråga sig om till exempel strftime() är en del av C eller inte.)
Man kan dessutom nöja sig med mycket mindre än halva Common Lisp. För flera år sedan gick jag ett par duster med språket (en av flera utmärkta böcker finns online här), men lyckades båda gångerna glömma allting eftersom andra intressen tog överhanden. Tredje gången jag försökte så gick jag en annan väg, och provade Scheme i stället. Scheme är en Lisp-dialekt, sådana finns det flera, och dessutom en relativt liten och konsekvent sådan. Nu är det mitt favoritspråk.
Scheme passar också perfekt för det jag kommit fram till att bakändan av H.VHS ska göra. Det är visserligen ett tolkat skriptspråk, men kan i regel kompileras till maskinkod. Samtidigt behåller i regel kompilerade Scheme-program möjligheten att tolka skriptfiler. Till på köpet är kärnan i vad som gör Lisp till Lisp att det är ett idealiskt språk att göra nya språk i. Det passar helt enkelt perfekt.
En annan fördel, kanske den största, är att om man använder något Lisp-språk, så sällar man sig till den kanske enda gruppen kodare som är självgoda nog att se ned på både Python och Perl.
Den stora nackdelen är förstås att det är så få som pratar Scheme, eller Lisp över huvud taget. Och jag kan väl erkänna att när jag inte lekt med språket på något år, så lyckas inte heller jag se skogen för alla parenteser.
Vad är det här med parenteserna då? Tja, "vanliga" programspråk, alltså alla de som (på ett ungefär) härstammar från FORTRAN (1954) i stället för LISP (1958), de körs på ett sådant sätt att man börjar med den första raden, och trallar sedan vidare tills man nått den sista.
Lisp är däremot (mer eller mindre) funktionellt, tillsammans med några andra udda fåglar, och körs inifrån-och-ut. Kanske beror skillnaden på att FORTRAN kom till som en abstraktion för hur datorer faktiskt fungerade, medan LISP egentligen bara var tänkt som ett mindre klumpigt alternativ till den helt teoretiska Turing-maskinen. Lisp står närmare matematiken än tekniken. För de flesta andra språken, bland annat just C++, gäller definitivt det motsatta.
Ett exempel. Av någon anledning vill jag ha en funktion som tar emot ett heltal x och returnerar en jeopardy-fråga, enligt följande regler.
x är negativt och jämnt: "Vad är noll minus roten ur [x*x]?"
x är udda: "Vad är roten ur [(x-1)*(x-1)] plus ett?"
x är 0: "What is mind? No matter. What is matter? Never mind."
Utan att försöka skapa särskilt elegant kod i någotdera språk, utan snarare demonstrativ, så kommer här varianter i C++ och Scheme.
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
string jeopardy(int x) {
stringstream sp;
if (x > 0 && x % 2 == 0) {
sp << x * x;
return (string)"Vad är roten ur "
+ sp.str() + "?";
}
else if (x > 0) {
sp << (x - 1) * (x - 1);
return (string)"Vad är roten ur "
+ sp.str() + " plus ett?";
}
else if (x != 0) {
sp << x * x;
return (string)"Vad är noll minus roten ur "
+ sp.str() + "?";
}
else {
return "What is mind? No matter. What is matter? Never mind.";
}
}
int main() {
cout << jeopardy(5) << "\n"
<< jeopardy(-1) << "\n"
<< jeopardy(200) << "\n"
<< jeopardy(0) << "\n"
<< jeopardy(2147483647) << "\n"; // (??)
}Här är utmatningen.
Vad är roten ur 16 plus ett?
Vad är noll minus roten ur 1?
Vad är roten ur 40000?
What is mind? No matter. What is matter? Never mind.
Vad är roten ur 4 plus ett?
Vad hände förresten där på slutet? ;-)
Här är en Scheme-variant:
(use srfi-13)
(define (jeopardy x)
(string-concatenate
(list (cond ((< 0 x)
"Vad är roten ur ")
((= 0 x)
"What is mind? No Matter. What is matter? Never mind.")
(else
"Vad är noll minus roten ur "))
(cond ((or (and (< 0 x) (even? x)) (> 0 x))
(number->string (* x x)))
((< 0 x)
(number->string (* (- x 1) (- x 1))))
(else
""))
(cond ((and (< 0 x) (odd? x))
" plus ett?")
((= x 0)
"")
(else
"?")))))
(print (jeopardy 5))
(print (jeopardy -1))
(print (jeopardy 200))
(print (jeopardy 2147483647))) ; (Ha!)Körning resulterar i (nästan) samma output:
Vad är roten ur 16 plus ett?
Vad är noll minus roten ur 1?
Vad är roten ur 40000?
Vad är roten ur 4611686009837453316 plus ett?
Som synes så kan Scheme, till skillnad från C++, hantera tal i vilken storlek som helst, så länge datorns minne räcker.
Förutom parenteserna, och det faktum att ett funktionsanrop i Scheme blir (jeopardy x) i stället för jeopardy(x), så lägger en obekant betraktare ganska snabbt märke till prefix-notationen. Det som skrivs "5+6+7+8.5" i språk som C++ skrivs i Scheme alltså i stället "(+ 5 6 7 8.5)".
De här två sista skillnaderna är kanske allt som egentligen håller folk borta från Scheme, har jag en känsla av. Och kanske ett par saker till: rekursion och lambda.
Fundera över följande lilla söta snutt C++ (modifierad från Wikipedia), som räknar ut summan av fibonacci-seriens första 7 tal:
vector<int> termer = {1, 1, 2, 3, 5, 8, 13};
int total = 0;
std::for_each(termer.begin(),
termer.end(),
[&](int x) { total += x; });
cout << total;Det ser visserligen litet märkligt ut för att vara C++, men så vitt jag kan förstå så är det numera giltigt. Men helt säker är jag inte. Resultatet blir i vilket fall att summan av talen i termer skrivs ut. [&](int x) { total += x; } är en anonym funktion, en lambda, som skapas där den står och sedan försvinner.
På Scheme kan man säga samma sak så här:
(print (let loop ((termer '(1 1 2 3 5 8 13))
(total 0))
(if (null? termer)
total
(loop (cdr termer) (+ total (car termer))))))(let loop …) är för övrigt en omskrivning av en omskrivning av ett lambda-anrop. I själva verket skapas här en anonym funktion som tar emot en parameter ("termer"), sedan tilldelas variabeln loop en pekare till funktionen, så att den till sist kan anropa sig själv med hjälp av namnet loop. (Värt att tänka på i sammanhanget är att i Scheme så är alla variabler pekare, eller rättare sagt: De är symboler.)
Tack vare att rekursionen sker på slutet av funktionen, så blir det ändå en iteration, precis som med for_each, eftersom den nya instansen av funktionen kan ersätta den föregående i minnet utan att något går förlorat.
Men nu gör jag inte Scheme någon fullständig rättvisa. En något mera exakt motsvarighet till C++-koden ovan ser ut så här:
(let ((termer '(1 1 2 3 5 8 13)))
(apply (lambda (first . rest)
(apply + (cons first rest)))
termer))Men eftersom vi, som första parameter till apply, vill skicka en funktion som returnerar summan av sina parametrar, så är det förstås egentligen enklare att bara använda funktionen + direkt, i stället för att stoppa den i en lambda som ovan.
(let ((termer '(1 1 2 3 5 8 13))) (apply + termer))
Om detta är möjligt ens i den nya C++0x-standarden vet jag faktiskt inte. Och det finns förstås massor av roligare saker man kan göra än så här. Scheme-kod är strukturerad i sitt eget data-format, vilket innebär att alla Scheme-program också är nästlade listor (motsvarande arrays eller vektorer). Kombinationen av detta och att kompilerad Scheme-kod kan köra icke-kompilerad Scheme-kod, medför att man kan exponera hela programspråket för användaren på det här viset:
(eval (read))
Och – självfallet – att alla Scheme-program kan programmera om sig själva under tiden de körs. Eftersom jag vill pressa fram något slags skriptspråk till bakändan på H.VHS, så kan något annat knappast passa bättre; här är ju en fullständig programspråksmiljö redan på plats.
Att jag sedan tänker sätta ett filter mellan eventuella skript-kodare och (read) hör väl till saken – det finns sätt att skripta man hellre ber andra lära sig än det frenetiskt parentetiserande.
Hur, till slut, kan man integrera Scheme med C++-kod? Relativt lätt: Chicken Scheme är en Scheme-implementation som inte kompilerar direkt till maskinkod, utan till C-kod som sedan kan köras genom till exempel gcc. Det är alltså enkelt att arbeta med eller åt C-funktioner, liksom det är lätt att inkludera C-stumpar i Scheme-koden.
Något annat som är kul med Scheme är att man faktiskt får något att blogga om. Nästa gång det händer lovar jag att slänga fram något mera strukturerat innehåll än det här.
H.VHS 0.3.1, Handshake 9 och Qt-knöggel
Även om jag skrivit program som gör fler och mer avancerade saker än H.VHS, så har inget av dem någonsin blivit så omfattande. Förmodligen beror det inte minst på att jag brukat använda Scheme, medan detta är skrivet i C++, och givetvis att jag än så länge kodar hellre än smart i Qt/C++/Windows-miljön.
Men det ska inlägget inte handla om. När jag hade rivit ut och ersatt mer eller mindre allt som sker bakom kulisserna i programmet, lyckats knyppla ihop hämtningsmetoder för TV3, TV4, TV6 och TV8, och därför trodde mig vara färdig att släppa nästa version, så dök två problem upp.
- TV4 Play vägrade plötsligt komma överens med rtmpdump (och därför även librtmp, som H.VHS använder).
- På en helt färsk dator syntes inga SVG-ikoner i verktygsfälten.
Lösningen på det första problemet var enklare än jag tänkt mig: De senaste, inofficiella versionerna av librtmp har inga problem att hantera "Handshake 9"-problemet, som det visst kallas, så lösningen fick bli att rulla en egen librtmp-dll. Lätt värt besväret, eftersom programmet dessutom blir av med den statiska länkningen till PolarSSL, som är GPL-licensierat, till förmån för OpenSSL, som passar bättre in i licensblandningen.
Det krångligare problemet var att lista ut hur man egentligen får Qt att snällt ladda SVG-grafik. Frågan har ställts massor av gånger på nätet, men inga svar verkar ha hjälpt de stackarna som försökt. Det hela försvåras dessutom av att på datorer där hela Qt finns installerat så fungerar allting som det ska – det är först på användarsidan som felen uppstår.
Så steg ett är att se till att själv drabbas av samma problem redan i produktionsmiljön.
- Infoga följande kod i main().
if (config.isRelease())
{
QStringList pathList;
pathList.append(qApp->applicationDirPath());
qApp->setLibraryPaths(pathList);
}
Det är de tre raderna i krullklasen som gör arbetet. Eftersom det blir stökigt att lagra alla stödfiler direkt i bygg-katalogen (där även alla objektfiler hamnar) så vill jag inte ha det här bekymret annat än när jag bygger en release, som jag sedan kopierar till en katalog ("dist") där QtGui4.dll och alla de andra ligger och väntar.
Med detta uppstår problemet även på min knädator. Nästa steg är förstås att också lösa det.
- SVG-grafik kräver QtSvg4.dll, som i sin tur vill ha QtXml4.dll, så de ska ligga direkt i
dist-katalogen, tillsammans med binären.dist\HuggpunktVHS.exe
dist\QtGui4.dll
…
dist\QtSvg4.dll
dist\QtXml4.dll
Man kunde tro att det skulle räcka – minns jag inte fel så gjorde det faktiskt det någon gång tidigare – men icke!
- Till sist måste ett plugin distribueras tillsammans med binären, och de måste placeras riktigt i förhållande till den. Så här:
dist\imageformats\qsvg4.dll
Det ska räcka. Men beroende på vad man vill göra kan även de följande filerna vara bra att skicka med, inte minst därför att de behövs för att komponenter som QWebView ska kunna använda diverse bildformat:
dist\imageformats\qtiff4.dll
dist\imageformats\qmng4.dll
dist\imageformats\qgif4.dll
dist\imageformats\qico4.dll
dist\imageformats\qjpeg4.dll
dist\iconengines\qsvgicon4.dll
Så principen tycks vara, att när man som i steg 1 sagt åt Qt att enbart leta efter stödfiler i den katalog som binären ligger i, så begränsar sig Qt lydigt till att leta efter sina moduler där. Men när det kommer till plugins, så måste de ändå ligga i korrekta underkataloger till dist-katalogen.
Och vilka de korrekta underkatalogerna är, det ser man för övrigt här:
C:\QtSDK\Desktop\Qt\4.7.3\mingw\plugins
Så här är det inte utan att man börjar fundera på att bygga allting statiskt i stället.
Uppdatering 2 juli. Så pass tveksam kände jag mig efter att faktiskt ha tänkt över det här Qt-knögglet, att jag tänkte om. Någonstans kring version 0.4–0.5 kommer en stor del av programmet (den jag kallar Hugg.Play) att skrivas om i Scheme. C++ är helt enkelt inte tillräckligt roligt. Eventuellt kommer Qt-beroendet försvinna helt och hållet vid något senare tillfälle.
Det finns dock en del buggar i version 0.3.1 som vore bra att få ordning på så snart som möjligt, så 0.3.2 kommer släppas som planerat.
Byta plats på Esc och Caps lock
cPS LOCK ÄR EN FÖRBnnat störig tangent innan man lär sig låta bli den. Lyckligtvis går den att flytta på, utan att man för den sakens skull ska behöva pilla med själva tangentbordet.
Eftersom jag dessutom helst använder vi till texteditor (eller mera precist nvi), så vill jag ha Esc nära till hands. I kombination med att jag använder styrknopp istället för touchpad eller mus, så behöver jag nästan bara flytta på händerna när det kliar.
För att byta plats på Esc och Caps lock i X11 kör du följande kommandon i skalet:
$ touch ~/.Xmodmap
$ echo "remove Lock = Caps_Lock\nkeysym Escape = Caps_Lock\nkeysym Caps_Lock = Escape\nadd Lock = Caps_Lock">>~/.Xmodmap
Ovanstående skapar eller utökar i själva verket filen .Xmodmap med följande innehåll:
remove Lock = Caps_Lock
keysym Escape = Caps_Lock
keysym Caps_Lock = Escape
add Lock = Caps_Lock
Inställningarna börjar gälla när du startat om X11, eller kört följande kommando:
$ xmodmap ~/.Xmodmap
I Windows 2000 eller senare kan du flytta tangenter med programmet SharpKeys.
IPv6, .SE och en brödrost
IPv4-adresserna är äntligen slut, läser jag i Dagens Nyheter. De har talat med Jörgen Eriksson på Stiftelsen .SE om saken. Men vad sjutton är det han säger?
– Just nu påverkas vi inte alls. I förlängningen kommer de flesta att tvingas byta ut sina internetmodem eller routrar. Och de som har riktigt gamla datorer kommer inte att kunna använda dem.
Jörgen Eriksson gissar att datorer som nu har sju till tio år på nacken kommer att tvingas till skroten. Å andra sidan kommer övergången inte att ske över en natt.
Att den alldeles för försenade övergången till IPv6 kommer skapa en fullständigt makaber skrothög av modem, routrar, nätverksskrivare och andra grunkor med inbäddade operativsystem är ingen nyhet. Antingen sitter nätverksstacken på ROM eller så har företagen för länge sedan slutat göra uppdateringar, eftersom de tjänar pengar på att sälja nya apparater i stället.
Å andra sidan kan tillräckligt dumma switchar och dylikt klara övergången utan bekymmer, eftersom de helt enkelt inte tittar närmare på vad det är för något de skickar omkring. (Ett lysande exempel på vad jag menar med »enkelhet« i bloggens tagline.)
Men att man ska behöva skrota sju år gamla datorer låter besynnerligt. Varför då? För att hitta hårdkodade operativsystem måste man ju nästan söka sig ända tillbaka till TI-99/4A, och den första 386-datorn kom redan 1985. Övergången borde alltså inte kunna bli besvärligare än ett operativsystembyte.
Fast det är väl det som är kruxet. Äldre datorer klarar ju inte av att köra dessa moderna monster till OS som Microsoft spottat ur sig de senaste åren, även om XP SP1 – som klarar IPv6 – ska kunna hanka sig fram klart brukligt på flera maskiner som redan kommit upp i tonåren.
Om Apple har skapat samma problem för sina användare, eller om det kanske är deras huvudsakliga affärsidé som skulle vara problemet – alltså inlåsningen – det vet jag inte. Fast saken låter suspekt: OS X har ju IPv6-stöd via KAME (via FreeBSD), och KAME blev färdigt innan Apple började fasa ut PowerPC-stödet ur OSX.
Oavsett vilket, så är det inte hårdvaran det är fel på. Jag har kört IPv6 på 386:or från 1990, här och var i världen står servrar från 1980-talet och väntar på övergången, och…

Den här brödrosten är redo.
…just det. Om en sex år gammal brödrost kan köra IPv6, så ska det väl ändå gå på en dator som är bara något år äldre? Den som tvingas skrota sin dator har blivit blåst av vilka det nu är som försökt göra operativsystemet i fråga. Sannolikt inte för första gången.
Ändra och skriva ut skyddade PDF-dokument
I det här inlägget beskriver jag några olika sätt att kringgå utskrifts- och ändringsskydden i PDF-dokument. Om du struntar i varför det går, så kan du klicka här för att hoppa över inledningen, som förklarar varför detta är och alltid kommer vara möjligt.
Miniteori
En fördel för oss som inte gillar slentrianmässigt bruk av kopierings- eller redigeringsskydd, är att sådana skydd egentligen är teoretiskt omöjliga. Varför det är på det viset, kan förklaras rätt enkelt med ett Alice and Bob-exempel.
Alice kallar vi avsändaren av ett stycke information, till exempel ett dokument eller en film. Den mottagare som Alice tänker sig kallar vi för Bertil. Alice vill dela med sig av sin information till Bertil utan att Eva får tag på den, eftersom Eva kopierar eller redigerar allt hon kommer över. (Och häng med här: Om Eva inte gjorde detta, så skulle hon inte längre vara Eva, utan ännu en Bertil.)
När det gäller hemlig kommunikation mellan Alice och Bertil, så är det möjligt att hålla Eva utom hörhåll, till exempel genom att viska eller lämna över ett USB-minne. Men problemet för Alice, när hon publicerar ett skyddat PDF-dokument, är att Eva därmed får tillgång till dokumentet, och kan titta på det precis som Bertil.
En enkel tumregel, som aldrig slår fel, är att om man har tillgång till ett stycke information så kan man kopiera den. Och det man kan kopiera kan man också redigera.
Utskriftsskyddet är enkelt att besegra, eftersom det är korkat. Skyddet går nämligen enbart ut på att PDF-dokumentet berättar vad man får och inte, och när PDF-läsare sedan öppnar dokumentet, så stänger de i regel lydigt av alla otillåtna funktioner. Lösningen på problemet är alltså inte svårare än att man helt enkelt struntar i vad dokumentet säger att man får göra och inte.
Vad som däremot inte är lika lätt, är att komma åt åtkomstskyddade dokument som man inte har rätt nyckel, certifikat eller lösenord till. Det är visserligen genomförbart, men ofta inte särskilt praktiskt eller snabbt. Inte heller är det särskilt intressant att förfalska signerade dokument.
När du gör en oskyddad kopia enligt metoden nedan, så tas de enkla skydden bort, alltså de där som går ut på att dokumentet berättar vad man får göra eller inte, och hoppas att man är duktig och lyder. Resultatet blir ett rent PDF-dokument utan sådana förmaningar eller andra tillbehör.
Avlägsna utskrifts- och redigeringsskydd
Man kan skapa oskyddade kopior av skyddade PDF-dokument på flera intressanta sätt. Den som googlar kan till exempel hitta patchar för både ghostscript, xpdf och pdftools.
Men den metod jag beskriver här är den som är enklast att återge och genomföra, om än inte den mest praktiska att använda i längden. Det enda du egentligen behöver är qpdf.
Windows-användare får det enklast om de hämtar arkivet qpdf_vc6_exe.zip, och packar upp .exe-filen i en mapp som är enkel att hitta tillbaka till. Vill du inte använda kommandoprompten, så rekommenderar jag att du även hämtar det här zip-arkivet, och packar upp .bat-filen i samma mapp som du lade qpdf_vc6.exe.
Sedan är installationen färdig. För att ta bort alla skydd från ett PDF-dokument, så tar du tag i dokumentet, och släpper det på ikonen som heter Släpp skyddade PDF-dokument på mig. I samma mapp som PDF-dokumentet ligger dyker strax en oskyddad kopia av det upp.
Om du inte vill behöva trycka på en knapp varje gång du konverterat ett dokument, så sätter du ett dubbelkolon (alltså ::) framför raden pause i .bat-filen. (Men då kommer du inte hinna se vad som gick snett, om något gick snett.)
På andra operativsystem än Windows använder du qpdf så här:
$ ./qpdf --decrypt "Skyddat original.pdf" "Oskyddad kopia.pdf"
Och om du vill köra qpdf i Windows själv, utan .bat-fil:
C:\...>.\qpdf_vc6.exe --decrypt "Skyddat original.pdf" "Oskyddad kopia.pdf"
Kommandot innebär att qpdf ska göra en oskyddad kopia av dokumentet Skyddat original.pdf och spara den som Oskyddad kopia.pdf.
Ett annat sätt: doPDF
Ett alternativt sätt att skapa oskyddade PDF-dokument i Windows är att installera doPDF. Därigenom dyker det upp en ny skrivare på datorn, som heter doPDF. När du skriver ut ett dokument till den skrivaren, så blir du ombedd spara ett PDF-dokument någonstans. Du kan alltså göra PDF-dokument av vad som helst som går att skriva ut.
Om du till exempel skapat ett Word-dokument med typsnitt som inte får bäddas in i en PDF, så kan du göra det ändå, genom att skriva ut till den här skrivaren.
Om man vill använda den här alternativa metoden för att skapa oskyddade kopior av PDF-dokument, så fungerar den givetvis bara om PDF-originalen tillåter utskrift.
(Ett liknande program är PDFCreator. Fördelen är att det är öppen källkod, vilket doPDF inte är. Nackdelen är att installationsprogrammet på ett riktigt envist sätt försöker få dig att installera en reklamsnyltare också. Installerar du på slentrian, utan att granska varenda förbaskad kryssruta, så blir dina webbläsare kapade och fyllda med reklam, och varje steg du tar på nätet kartläggs fortsättningsvis av vad det nu är för reklamföretag.)
Åtkomstskyddade PDF-dokument
Det finns flera variationer på den här typen av skydd, och alla är lika irriterande. Vissa dokument kräver bara att man matar in rätt lösenord för att man ska få se dem, medan det är värre med andra. Vid något tillfälle köpte jag till exempel Språknämndens Svenska Skrivregler som PDF.
Det dokumentet går inte att öppna i Adobe Reader, som klagar över saknade "skyddsinsticksprogram". Efterhand som Adobe uppdaterat sin programvara har de funktioner som krävs för att öppna boken försvunnit, och nu är det alldeles för opraktiskt att komma åt innehållet.
Hade jag ansträngt mig litet medan jag fortfarande kunde öppna boken, så hade jag kunnat göra en kopia utan åtkomstskydd, men det var jag visst för bekväm för.
Så nu är jag inte någon Bertil längre, skulle man kunna säga. Adobes kunder för den här typen av dokumentskydd är förstås inte heller sådana som jag, alltså folk som vill läsa sina böcker, utan bokhandlarna. Och de lär inte protestera särskilt högljutt, för jag hade ju inget annat val än att köpa boken en gång till. Fast den gången blev det den mycket dyrare pappersvarianten.

